diff options
author | brent s | 2017-03-14 11:11:19 -0400 |
---|---|---|
committer | brent s | 2017-03-14 11:11:19 -0400 |
commit | 1107a76726104be3a8091be038251b9bef817194 (patch) | |
tree | f79914ca7c1cb5b1f3fd0ce32bdb9a7a243e50f1 | |
parent | c63d396359c1cf551e08a3ef94664fb3a84aea82 (diff) | |
download | aur-ietf-cli.tar.gz |
finalized package
-rw-r--r-- | .SRCINFO | 26 | ||||
-rw-r--r-- | LICENSE | 13 | ||||
-rw-r--r-- | LICENSE.sig | bin | 0 -> 566 bytes | |||
-rw-r--r-- | PKGBUILD | 43 | ||||
-rw-r--r-- | ietf | 1003 | ||||
-rw-r--r-- | ietf.README | 85 | ||||
-rw-r--r-- | ietf.README.sig | bin | 0 -> 566 bytes | |||
-rw-r--r-- | ietf.config | 52 | ||||
-rw-r--r-- | ietf.config.arch | 52 | ||||
-rw-r--r-- | ietf.config.arch.sig | bin | 0 -> 566 bytes | |||
-rw-r--r-- | ietf.config.sig | bin | 0 -> 566 bytes |
11 files changed, 1260 insertions, 14 deletions
@@ -1,17 +1,35 @@ -# Generated by aurpkgs -# Tue Mar 14 14:08:25 UTC 2017 +# Generated by mksrcinfo v8 +# Tue Mar 14 15:11:19 UTC 2017 pkgbase = ietf-cli pkgdesc = A commandline IETF RFC browser/utility pkgver = 1.14 - pkgrel = 1 + pkgrel = 2 url = https://trac.tools.ietf.org/tools/ietf-cli/ arch = i686 arch = x86_64 - license = WTFPL + license = custom depends = python + optdepends = lynx: the default web browser specified in the config source = http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf + source = http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf.config + source = ietf.config.arch + source = http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf.README + source = LICENSE source = ietf.sig + source = ietf.config.sig + source = ietf.config.arch.sig + source = ietf.README.sig + source = LICENSE.sig sha512sums = cf16db6148c1110c6ff60995ada6354da3227be7ecf5e76df68ee05843a602fd5cf1e9ba1de9128efa84fc1c1e33de69d9db08c58f1cb565bdafe3e76191a6f0 + sha512sums = 68563ec320541618df4a91c942fa7062b34fa71d5761ad68134b92205dc9b6b6d95f20e0db6c8e6bf421d0d255ea15a9b60de1665faa1be9eff6e4eae5462a2a + sha512sums = 9a0dc6f5a0bdaf15779568e177026f9c40a82b77564adb83eabf0a35e05e13fa070f2ab736fa04734721954169d00d8b4d4621d92138f4dee99fa3ce6076be52 + sha512sums = 871fb110699ecccbab9a96ecbbecbd7f28035fcf2caea6375d776c41ee683b0a38c6b982ce4bbb9441f069e57b225d0c3667db5affa0dbc85d622e6d505a3ca5 + sha512sums = 29dc31d0b0365f8b5037c846eb7f441f38249d25cf7aeba134777dddc422ff9faff92aded93bc03b21b4390153568543b2be0c92d09c667559dd78390654e70e + sha512sums = SKIP + sha512sums = SKIP + sha512sums = SKIP + sha512sums = SKIP sha512sums = SKIP pkgname = ietf-cli + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..456c4889bf60 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/LICENSE.sig b/LICENSE.sig Binary files differnew file mode 100644 index 000000000000..15488069d876 --- /dev/null +++ b/LICENSE.sig @@ -4,25 +4,48 @@ validpgpkeys=('748231EBCBD808A14F5E85D28C004C2F93481F6B') # News updates for packages can be followed at https://devblog.square-r00t.net pkgname=ietf-cli pkgver=1.14 -pkgrel=1 +pkgrel=2 pkgdesc="A commandline IETF RFC browser/utility" arch=( 'i686' 'x86_64' ) url="https://trac.tools.ietf.org/tools/ietf-cli/" -license=( 'WTFPL' ) +license=('custom') depends=( 'python' ) -_pkgname=ietf-cli +optdepends=( 'lynx: the default web browser specified in the config' ) +_pkgname=ietf install= changelog= noextract=() source=("http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf" - "ietf.sig") + "http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf.config" + "ietf.config.arch" + "http://svn.tools.ietf.org/svn/tools/ietf-cli/ietf.README" + "LICENSE" + "ietf.sig" + "ietf.config.sig" + "ietf.config.arch.sig" + "ietf.README.sig" + "LICENSE.sig") sha512sums=('cf16db6148c1110c6ff60995ada6354da3227be7ecf5e76df68ee05843a602fd5cf1e9ba1de9128efa84fc1c1e33de69d9db08c58f1cb565bdafe3e76191a6f0' - 'SKIP') -build() { - cd "${srcdir}/${_pkgname}/src" - make prefix=${pkgdir}/usr + '68563ec320541618df4a91c942fa7062b34fa71d5761ad68134b92205dc9b6b6d95f20e0db6c8e6bf421d0d255ea15a9b60de1665faa1be9eff6e4eae5462a2a' + '9a0dc6f5a0bdaf15779568e177026f9c40a82b77564adb83eabf0a35e05e13fa070f2ab736fa04734721954169d00d8b4d4621d92138f4dee99fa3ce6076be52' + '871fb110699ecccbab9a96ecbbecbd7f28035fcf2caea6375d776c41ee683b0a38c6b982ce4bbb9441f069e57b225d0c3667db5affa0dbc85d622e6d505a3ca5' + '29dc31d0b0365f8b5037c846eb7f441f38249d25cf7aeba134777dddc422ff9faff92aded93bc03b21b4390153568543b2be0c92d09c667559dd78390654e70e' + 'SKIP' + 'SKIP' + 'SKIP' + 'SKIP' + 'SKIP') + +prepare() { + # We need to make this a little more package-friendly. Currently uses ("~/bin/ietf.config", "/usr/local/bin/ietf.config", "~/.ietf/ietf.config") + cd ${srcdir} + sed -i -re "s/^ConfigPlaces[[:space:]]*=[[:space:]]*\(.*$/ConfigPlaces = ('~\/.${_pkgname}.config', '~\/.${_pkgname}\/${_pkgname}.config', '\/etc\/${pkgname}\/${_pkgname}.config')/" ${_pkgname} } + package() { - install -D -m755 ${srcdir}/${_pkgname}/src/${_pkgname} ${pkgdir}/usr/bin/${_pkgname} - install -D -m644 ${srcdir}/${_pkgname}/docs/README.html.en ${pkgdir}/usr/share/doc/${_pkgname}/README.html + install -D -m755 ${srcdir}/${_pkgname} ${pkgdir}/usr/bin/${_pkgname} + install -D -m644 ${srcdir}/${_pkgname}.README ${pkgdir}/usr/share/doc/${pkgname}/README + install -D -m644 ${srcdir}/${_pkgname}.config.arch ${pkgdir}/etc/${pkgname}/${_pkgname}.config + install -D -m644 ${srcdir}/${_pkgname}.config ${pkgdir}/usr/share/doc/${pkgname}/${_pkgname}.config + install -D -m644 ${srcdir}/LICENSE ${pkgdir}/usr/share/licenses/${pkgname}/WTFPL } @@ -0,0 +1,1003 @@ +#!/usr/bin/python +from __future__ import print_function +from cmd import Cmd +from fnmatch import filter as fnfilter +from glob import glob +from json import dump as jsondump, load as jsonload +from optparse import OptionParser +from os import chdir, environ, link, listdir, mkdir, system, unlink +from os.path import basename, exists as pathexists, expanduser, isdir, join as pathjoin +from pdb import set_trace as trace +from re import compile, search, sub +from subprocess import Popen, PIPE +from sys import argv, excepthook +from time import strftime +from xml.etree import ElementTree as ElTree + +''' +Program to help command-line users have better access to IETF-related documents + See help text below. +''' +__version__ = "1.14" +__license__ = "https://en.wikipedia.org/wiki/WTFPL" + +# Version history: +# 1.0 +# Initial release +# 1.1 +# Added bcp +# Split out the command-calling in the CLI to make it clearer +# 1.2 +# Added more places to look for the config file +# Fixed help text for "charter" to make wildcard use clearer +# Added auth48 +# 1.3 +# Added the WTFPL license +# Added "rfcextra " which opens RFCs that replace the requested RFC and the errata page for the +# requested RFC if the database says there is errata +# Added "rfcstatus" which lists RFC status from the RFC Editor's database +# Added "draftstatus" which lists the I-D status from the Datatracer +# Added "author" command which lists all drafts and RFCs by an author +# Added parsing the Datatracker's I-D database during mirror; file is saved as a local JSON database +# Added parsing the RFC Editor's database during mirror; file is saved as a local JSON database +# Added the --quiet command line option +# Made "tracker" also work for RFCs +# Made the help a bit more helpful +# Fixed bug that found too many drafts in in-notes/authors +# Fixed bug so that the help text fit in 80 column windows +# Fixed bug for finding the configuration file +# 1.4 +# Added "iesg docs" and "iesg agenda" +# Fixed bug with displaying multiple drafts that have the same name beginnings +# 1.5 +# Made changes to get new-style charters with "charter-new" +# 1.6 +# Added "charternew" +# Added "conflict" +# 1.7 +# Got rid of "charter" because new WGs don't work with old charter +# 1.8 +# Added .encode('utf-8') to author and AD names in draftstatus because those might be UTF-8 +# Clearly, this needs to be dealt with better in a future version +# 1.9 +# Added "rfcinfo", and changed "tools" to only go to the tools.ietf.org site +# 1.10 +# Changed the rsync targets from www.ietf.org to rsync.ietf.org +# 1.11 +# Mirror conflict reviews and status changes when updating, but don't expose them in the UI +# 1.12 +# Changed all http: URIs to https: +# 1.13 +# Fixed ietf-rfc-status.json generation, and fixed a minor typo +# 1.14 +# Changed the location of the IESG directory, which had been broken for a while + +########## +# Utility functions and definitions +########## + +KnownCmds = ("auth48", "author", "bcp", "charter", "conflict", "diff", "draft", "draftstatus", "iesg", "mirror", \ + "rfc", "rfcextra", "rfcinfo", "rfcstatus", "tools", "tracker", "foo") +ConfigPlaces = ("~/bin/ietf.config", "/usr/local/bin/ietf.config", "~/.ietf/ietf.config") +RFCZerosPat = compile(r'^0+(.*)') + +# Make a block of text that can be executed in the CLI +CLICmdCode = "" +for ThisCmd in KnownCmds: + ThisCode = ''' +def do_REPLTHISCMD(self, RestOfArgs): + Cmd_REPLTHISCMD(RestOfArgs.split(' ')) +def help_REPLTHISCMD(self): + CheckHelp('REPLTHISCMD', '__helptext__') +''' + CLICmdCode += ThisCode.replace("REPLTHISCMD", ThisCmd) + +# Find a draft in the in-notes/authors directory, return "rfc1234" or "" +def FindDraftInAuth48(basename): + TheDiffs = glob(pathjoin(FullRFCDir, "authors", "*-diff.html")) + for ThisDiff in TheDiffs: + try: + InTextLines = open(ThisDiff, mode="r").readlines() + except: + exit("Weird: could not read '" + ThisDiff + "' even though it exists. Exit.") + for InText in InTextLines[0:40]: + if InText.find("<strike><font color='red'>" + basename) > -1: + return(ThisDiff.replace(pathjoin(FullRFCDir, "authors", ""), "").replace("-diff.html", "")) + return("") # Only here if there was no file in AUTH48 + +# Open a URL in the browser, but give a warning in the terminal if the command is "less" +def WebDisplay(TheURL, TheArg): + TheRet = system(DisplayWebCommand + TheURL + TheArg) + if TheRet > 0: + print("The command used to display web content, '" + DisplayWebCommand \ + + TheURL + TheArg + "', had an error.'") + if DisplayWebCommand == "less ": + print("The reason that this HTML was displayed on your console is that you do not have\n" \ + "'DisplayWebCommand' defined in the file '" + ConfigFile + "'.") + +# Create a command-line processor for our commands +class OurCLI(Cmd): + intro = "Command line processor for ietf commands; try 'help' for more info." + prompt = "ietf: " + # Make just pressing Return not do anything + def emptyline(self): + pass + # Make it easy to exit + def do_exit(self, RestOfArgs): + return True + do_quit = do_q = do_exit + def do_EOF(self, RestOfArgs): + print() + return True + def default(self, RestOfArgs): + print("Unknown command '" + RestOfArgs + "'. Try 'help' for a list of commands.") + # Let them do shell commands + def do_shell(self, RestOfArgs): + print("Execuiting shell command: '" + RestOfArgs + "'") + system(RestOfArgs) + # Fill in the needed definitions for all the known commands + # This was created as CLICmdCode above + exec(CLICmdCode) + # Do our own help + def do_help(self, RestOfArgs): + if RestOfArgs in KnownCmds: + CheckHelp(RestOfArgs, "__helptext__") + else: + CheckHelp("allclicmds", "__helptext__") + # Allow to change commandline settings + def do_tombstones(self, RestOfArgs): + global DisplayTombstones + DisplayTombstones = True + def do_maxdrafts(self, RestOfArgs): + try: + global MaxDrafts + MaxDrafts = int(RestOfArgs) + except: + exit("The argument to 'maxdrafts' must be a positive integer. Exiting.") + def do_usedraftnumbers(self, RestOfArgs): + global UseDraftNumbers + UseDraftNumbers = True + def do_quiet(self, RestOfArgs): + global QuietDraft + QuietDraft = True + +# Print help text if this is called with no args or with a single arg of "__helptext__" +# All commands other than "mirror" need args. +def CheckHelp(TheCaller, InArgs): + if ((InArgs == "__helptext__") or ((InArgs == []) and (TheCaller != "mirror"))): + if HelpText.get(TheCaller, "") != "": + print(HelpText[TheCaller]) + else: + print("No help text available for '" + TheCaller + "'.") + return True + else: + return False + +HelpText = { + "auth48": '''auth48: + Takes a list of RFC numbers or draft names, determines if there are AUTH48 + files associated with them, and displays the various files.''', + "bcp": '''bcp: + Takes a list of BCP numbers. Displays the BCP RFCs found using the text + dispay program. You can also give 'index' as an argument to see + bcp-index.txt.''', + "charter": '''charter: + Takes a list of WG names. Displays the charter for each WG using the text + dispay program. Wildcards are appended to the beginning and end of the + charter name given, and can also be given in the name. The charters are + gotten from the new-style charters in the "charter" directory, which was + begun in June 2012.''', + "conflict": '''conflict: + Takes a draft name (with or without the '-nn' version number or '.txt'' + and displays the HTML conflict review, if it exists.''', + "diff": '''diff: + Takes a draft name (with or without the '-nn' version number or '.txt' + and displays the HTML diff between it and the preceding version on the + IETF Tools page using your web display program.''', + "draft": '''draft: + Takes a list of draft file names. Displays the drafts found using the text + dispay program. Substrings can be used instead of full names. There are + command-line options to change the way this shows tombstones (where a + draft has expired or been replaced with an RFC). You can also give + 'abstracts' as an argument to see 1id-abstracts.txt.''', + "draftstatus": '''draftstatus: + Takes a list of draft names or substrings and reports the status from the + Datatracker database for each one''', + "iesg": '''iesg: + Displays the next agenda (when given the "agenda" argument") or the list + of documents under consideration (when given the "docs" argument) in the + web display program''', + "mirror": '''mirror: + Updates your local mirror of IETF directories, such as all drafts, RFCs, + and WG charters.''', + "rfc": '''rfc: + Takes a list of RFC file names. Displays the RFCs found using the text + dispay program. You do not need to give 'rfc' or '.txt' in the file + names. You can also give 'index' as an argument to see rfc-index.txt. + This command searches both the main RFC directory and the pre-publication + (AUTH48) directory. It will automatically open RFCs that obsolete and + update the one given, and will open errata in the browser if the RFC + Editor's database indicates that such errata exists.''', + "rfcextra": '''rfcextra: + Similar to 'rfc' but opens additional files. It will automatically open + RFCs that obsolete and update the one given, and will open errata in the + browser if the RFC Editor's database indicates that such errata exists.''', + "rfcinfo": '''rfcinfo: + Takes a list of RFC numbers and opens the info pages from the RFC Editor's + web site''', + "rfcstatus": '''rfcstatus: + Takes a list of RFC numbers and reports the status from the RFC Editor's + database for each one''', + "tools": '''tools: + Takes a list of draft file names, RFC names, and/or WG names. Displays the + result from the IETF Tools pages in the web dispay program. Draft names + can be either complete or be missing the '-nn' version number and '.txt'. + RFC names can be given as 'rfc1234' or '1234'. WG names are matched + exactly.''', + "tracker": '''tracker: + Takes a list of draft file names and/or WG names. Displays the + result from the IETF Datatracker pages in the web dispay program. Draft + names and WG names are matched exactly.''', +} +AllHelp = "Command-line interface for displaying IETF-related information. Version " \ + + __version__ + ".\nCommands are:\n" +for ThisHelp in sorted(HelpText.keys()): + AllHelp += " " + HelpText[ThisHelp] + "\n" +ArgsCLIHelp = "You can cause tombstone drafts to be displayed in the 'draft' command\n" \ + + " by giving the 'tombstones' command by itself.\n" \ + + "You can increase the number of drafts that will be opened by the 'draft'\n" \ + + " command by giving the 'maxdrafts' command followed by an integer.\n" \ + + "You can require that the 'draft' command only use full draft names\n" \ + + " (including draft numbers and '.txt') by giving the 'usedraftnumbers'\n" \ + + " command by itself.\n" \ + + "You can make the 'draft' command not tell you about tombstones by giving\n" \ + + " the 'quiet' command by itself.\n" +AllCLIHelp = AllHelp + ArgsCLIHelp \ + + "There is also a 'shell' command to give shell commands from within\n" \ + + " this processor.\n" \ + + "Use 'q' or 'quit' or 'exit' to leave the program." +ArgsShellHelp = "You can cause tombstone drafts to be displayed in the 'draft' command\n" \ + + " with the --tombstones argument.\n" \ + + "You can increase the number of drafts that will be opened by the 'draft'\n" \ + + " command with the --maxdrafts= argument followed by an integer.\n" \ + + "You can require that the 'draft' command only use full draft names\n" \ + + " (including draft numbers and '.txt') with the --usedraftnumbers'\n" \ + + " argument.\n" \ + + "You can make the 'draft' command not tell you about tombstones with the\n" \ + + " --quiet argument.\n" +AllShellHelp = AllHelp + ArgsShellHelp +HelpText["allclicmds"] = AllCLIHelp +HelpText["allshellcmds"] = AllShellHelp + +########## +# The commands themselves +########## + +### auth48 -- Open all appropriate files for a doc in AUTH48 +def Cmd_auth48(Args): + if CheckHelp("auth48", Args): return + if Args[0] == "": + print("Must give at least one draft name or RFC name; skipping.") + return + def ShowAuth48s(RFCfile): + # Incoming file is in format "rfc1234" + # Open the text file + system(DisplayTextCommand + pathjoin(FullRFCDir, "authors", RFCfile + ".txt")) + # Open the local diff in the browser + WebDisplay("file:///", pathjoin(FullRFCDir, "authors", RFCfile + "-diff.html")) + # Show the status on the RFC Editor's site + WebDisplay("https://www.rfc-editor.org/auth48/", RFCfile) + for ThisArg in Args: + # If it is just a number, check for the RFC + if ThisArg.isdigit(): + if pathexists(pathjoin(FullRFCDir, "authors", "rfc" + ThisArg + ".txt")): + ShowAuth48s("rfc" + ThisArg) + else: + print("You specified an all-digit argument, '" + ThisArg + "', but a corresponding RFC doesn't " \ + + "exist in the AUTH48 directory. Skipping.") + elif ((ThisArg[0:3] == "rfc") and (ThisArg[3:7].isdigit())): + if pathexists(pathjoin(FullRFCDir, "authors", "rfc" + ThisArg[3:7] + ".txt")): + ShowAuth48s("rfc" + ThisArg[3:7]) + else: + print("You specified 'rfc' and some digits, but a corresponding RFC doesn't " \ + + "exist in the AUTH48 directory. Skipping.") + elif ThisArg.startswith("draft-"): + ThisBaseName = basename(ThisArg) + ThisAuth48 = FindDraftInAuth48(ThisBaseName) + if ThisAuth48 != "": + ShowAuth48s(ThisAuth48) + else: + print("You gave a draft name, but that draft doesn't have an AUTH48 RFC associated with it. Skipping.") + else: + print("Didn't recognize the argument '" + ThisArg + "'. Skipping.") + +### author -- Search for drafts and RFCs with a particular author +def Cmd_author(Args): + if CheckHelp("author", Args): return + if Args[0] == "": + print("Must give at least one string to search for; skipping.") + return + # Get the drafts status and RFC status databases + try: + with open(IDStatusFileLoc, mode="r") as statusf: + IDStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the ID status database, '" + IDStatusFileLoc + "'. Exiting.") + try: + with open(RFCStatusFileLoc, mode="r") as statusf: + RFCStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the RFC status database, '" + RFCStatusFileLoc + "'. Exiting.") + for ThisArg in Args: + FoundRFCs = [] + FoundIDs = [] + for ThisRFC in sorted(RFCStatusDB.keys()): + if search(".*" + ThisArg + ".*", str(RFCStatusDB[ThisRFC]["authors"])): + FoundRFCs.append(ThisRFC) + if FoundRFCs: + print("Found '" + ThisArg + "' as author in RFCs:") + for ThisFoundRFC in FoundRFCs: + print(" RFC " + ThisFoundRFC + " " + RFCStatusDB[ThisFoundRFC]["title"]) + for ThisID in sorted(IDStatusDB.keys()): + if search(".*" + ThisArg + ".*", repr(IDStatusDB[ThisID]["authors"])): + FoundIDs.append(ThisID) + if FoundIDs: + print("Found '" + ThisArg + "' as author in IDs:") + for ThisFoundID in FoundIDs: + print(" " + ThisFoundID + " " + IDStatusDB[ThisFoundID]["title"]) + +### bcp -- Open BCPs locally +def Cmd_bcp(Args): + if CheckHelp("bcp", Args): return + if Args[0] == "": + print("Must give at least one BCP number; skipping.") + return + for ThisArg in Args: + # Special case: 'index' returns the bcp-index.txt file + if ThisArg == "index": + system(DisplayTextCommand + pathjoin(FullRFCDir, "bcp-index.txt")) + else: + for ThisBCPNum in Args: + ThisBCPFile = pathjoin(FullRFCDir, "bcp", "bcp"+ ThisBCPNum + ".txt") + if pathexists(ThisBCPFile): + system(DisplayTextCommand + ThisBCPFile) + else: + print("Could not find the BCP " + ThisBCPNum + " as '" + ThisBCPFile + "'; skipping.") + +### FillAllWGsInIETF -- Helper function for speeding up lookup of new-style charters +def FillAllWGsInIETF(): + # Get this list once to optimize if there are many WGs to look up + try: + chdir(expanduser(CharterDir)) + except: + exit("Weird: could not chdir to " + CharterDir) + global AllWGsInIETF + AllWGsInIETF = {} + AllCharterFiles = glob(pathjoin(CharterDir, "charter-ietf-*")) + for ThisCharterFile in sorted(glob("charter-ietf-*")): + CharterParts = (ThisCharterFile[13:-4]).split("-") + # There's always a special case for the Security Area <grumble> + if CharterParts[0:2] == ["krb", "wg"]: + CharterParts = [ "krb-wg", CharterParts[2:] ] + AllWGsInIETF[CharterParts[0]] = ThisCharterFile + +### charter -- Open 2012-style charter files locally +def Cmd_charter(Args): + if CheckHelp("charternew", Args): return + if Args[0] == "": + print("Must give at least one WG name; skipping.") + return + FillAllWGsInIETF() + for ThisArg in Args: + MatchingWGs = fnfilter(sorted(AllWGsInIETF.keys()), "*" + ThisArg + "*") + if len(MatchingWGs) > 10: + AllMatched = ", ".join(MatchingWGs) + print("More than 10 WGs match '*" + ThisArg + "*' in the IETF directory. Skipping.\n" + AllMatched) + elif len(MatchingWGs) == 0: + print("Did not find the WG that matches '*" + ThisArg + "*' in the IETF directory.") + print("Possibly try the 'tracker' command to see if the Datatracker has the desired data. Skipping.") + else: + for ThisWG in MatchingWGs: + CharterTextFile = pathjoin(expanduser(CharterDir), AllWGsInIETF[ThisWG]) + if pathexists(CharterTextFile): + system(DisplayTextCommand + CharterTextFile) + else: + print("Weird: when looking for the charter file for " + ThisWG + ", I should have found " \ + + CharterTextFile + ", but didn't. Skipping.") + +### conflict -- Show the conflict review for a draft +def Cmd_conflict(Args): + if CheckHelp("conflict", Args): return + if Args[0] == "": + print("Must give at least one draft name; skipping.") + return + for ThisArg in Args: + if ThisArg.startswith("draft-"): + # Strip any ".txt" and "-nn" from the arugment so we can match the database + ShorterArg = sub(r'(\.txt)$', "", ThisArg) + ShorterArg = sub(r'-\d\d$', "", ShorterArg) + # Remove "draft-" from the beginning + ShorterArg = ShorterArg[6:] + WebDisplay("https://datatracker.ietf.org/doc/conflict-review-", ShorterArg) + else: + print("The argument to this command must begin with 'draft-'.\n") + +### diff -- Show the diff between a draft and the previous one on the IETF Tools site +def Cmd_diff(Args): + if CheckHelp("diff", Args): return + if Args[0] == "": + print("Must give at least one draft name; skipping.") + return + for ThisArg in Args: + if ThisArg.startswith("draft-"): + WebDisplay("https://tools.ietf.org/rfcdiff?url2=", ThisArg) + else: + print("The argument to this command must begin with 'draft-'.\n") + +### draft -- Open drafts locally +def Cmd_draft(Args): + if CheckHelp("draft", Args): return + if Args[0] == "": + print("Must give at least one draft name; skipping.") + return + # Get the drafts status database + try: + with open(IDStatusFileLoc, mode="r") as statusf: + IDStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the ID status database, '" + IDStatusFileLoc + "'. Exiting.") + for ThisArg in Args: + # Special case: 'abstracts" returns the 1id-abstracts.txt file + if ThisArg == "abstracts": + system(DisplayTextCommand + pathjoin(FullIDDir, "1id-abstracts.txt")) + continue + # Pay attention only to expired, became-an-rfc, was replaced, and active drafts + MatchedDraftsByStatus = { "Expired": [], "RFC": [], "Replaced": [], "Active": [] } + # Strip any ".txt" and "-nn" from the arugment so we can match the database + ShorterArg = sub(r'(\.txt)$', "", ThisArg) + ShorterArg = sub(r'-\d\d$', "", ShorterArg) + # Find all the drafts in the database that match the argument given + for ThisDraftFromDraftsDB in IDStatusDB.keys(): + if search(".*" + ShorterArg + ".*", ThisDraftFromDraftsDB): + ThisStatus = IDStatusDB[ThisDraftFromDraftsDB]["status"] + if ThisStatus in MatchedDraftsByStatus.keys(): + MatchedDraftsByStatus[ThisStatus].append(ThisDraftFromDraftsDB) + # Report on the drafts found for expired, became an RFC, and replaced + if not(QuietDraft): + if MatchedDraftsByStatus["Expired"]: + print("Matching drafts that have expired:") + for ThisExpired in sorted(MatchedDraftsByStatus["Expired"]): + print(" " + ThisExpired + " (last revised " + IDStatusDB[ThisExpired]["last-revised"] + ")") + print() + if MatchedDraftsByStatus["RFC"]: + print("Matching drafts that became RFCs:") + for ThisBecameRFC in sorted(MatchedDraftsByStatus["RFC"]): + print(" " + ThisBecameRFC + " (became RFC " + IDStatusDB[ThisBecameRFC]["became-rfc"] + ")") + print() + if MatchedDraftsByStatus["Replaced"]: + print("Matching drafts that were replaced:") + for ThisWasReplaced in sorted(MatchedDraftsByStatus["Replaced"]): + print(" " + ThisWasReplaced + " (replaced by " + IDStatusDB[ThisWasReplaced]["replaced-by"] + ")") + print() + # If there are no active drafts that match this argument, say something and go to the next argument + if not(MatchedDraftsByStatus.get("Active")): + print("No active drafts matched the substring '" + ThisArg + "'.") + continue + # If there are too many matched active drafts, list them and go to the next argument + if len(MatchedDraftsByStatus["Active"]) > MaxDrafts: + print("There are more than " + str(MaxDrafts) + " active drafts that match the string '" \ + + ThisArg + "'; not displaying.\nYou can raise this count with ", end="") + if FromCommandLine: + print(" the '--maxdrafts' command-line argument,\nsuch as '--maxdrafts=40'.") + else: + print(" the 'maxdrafts' command,\nsuch as 'maxdrafts 40'.") + for ThisOverMax in MatchedDraftsByStatus["Active"]: + print(" " + ThisOverMax) + continue + # Display the active drafts that match this argument + for ThisActiveDraft in sorted(MatchedDraftsByStatus["Active"]): + # If it is in Auth48, display it from that directory only + ThisAuth48 = FindDraftInAuth48(ThisActiveDraft) + if ThisAuth48 != "": + print("This Internet-Draft is in AUTH48 state; displaying " + ThisAuth48) + WebDisplay("file:///", pathjoin(FullRFCDir, "authors", ThisAuth48 + "-diff.html")) + continue + # Display the draft from the numbered or unnumbered mirror directory, based on their preference + if UseDraftNumbers: + TargetDir = FullIDDir + else: + TargetDir = FullShortIDDir + # Make sure there is only one that matches + TheseNumberedDrafts = glob(pathjoin(TargetDir, ThisActiveDraft + "*")) + if len(TheseNumberedDrafts) == 0: + print("Weird: could not find a draft matching '" + ThisActiveDraft \ + + "' in '" + TargetDir + "'; skipping.") + else: + for ThisToDisplay in TheseNumberedDrafts: + system(DisplayTextCommand + pathjoin(TargetDir, ThisToDisplay)) + +### draftstatus -- Show I-D status from the database without opening the file +def Cmd_draftstatus(Args): + if CheckHelp("draftstatus", Args): return + if Args[0] == "": + print("Must give at least one draft name or substring; skipping.") + return + # Open the status database before going through the arguments + try: + with open(IDStatusFileLoc, mode="r") as statusf: + IDStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the ID status database, '" + IDStatusFileLoc + "'. Exiting.") + for ThisArg in Args: + FoundThisArg = False + # Find all drafts matching this string + for ThisDraftFromDraftsDB in IDStatusDB.keys(): + if search(".*" + ThisArg + ".*", ThisDraftFromDraftsDB): + FoundThisArg = True + ThisIDStatus = IDStatusDB.get(ThisDraftFromDraftsDB) + print("Draft " + ThisDraftFromDraftsDB + ":\n Status: " + ThisIDStatus["status"]) + if ThisIDStatus.get("title"): + print(" Draft title: " + ThisIDStatus.get("title")) + if ThisIDStatus.get("authors"): + print(" Authors: " + ThisIDStatus.get("authors").encode('utf-8')) + if ThisIDStatus.get("last-revised"): + print(" Last revision: " + ThisIDStatus.get("last-revised")) + if ThisIDStatus.get("iesg-state"): + print(" IESG state: " + ThisIDStatus.get("iesg-state")) + if ThisIDStatus.get("intended-level"): + print(" Intended level: " + ThisIDStatus.get("intended-level")) + if ThisIDStatus.get("last-call-ends"): + print(" Last call ends: " + ThisIDStatus.get("last-call-ends")) + if ThisIDStatus.get("became-rfc"): + print(" Became RFC: " + ThisIDStatus.get("became-rfc")) + if ThisIDStatus.get("replaced-by"): + print(" Replaced by: " + ThisIDStatus.get("replaced-by")) + if ThisIDStatus.get("wg-name"): + print(" WG: " + ThisIDStatus.get("wg-name")) + if ThisIDStatus.get("area-name"): + print(" Area: " + ThisIDStatus.get("area-name")) + if ThisIDStatus.get("ad-name"): + print(" Area Director: " + ThisIDStatus.get("ad-name").encode('utf-8')) + if ThisIDStatus.get("file-types"): + # No need to show just .txt + if ThisIDStatus.get("file-types") != ".txt": + print(" File types available: " + ThisIDStatus.get("file-types")) + if FoundThisArg == False: + print("Did not find any records in the database matching " + ThisArg + "; skipping.") + +### iesg -- Show IESG pages on the Datatracker +def Cmd_iesg(Args): + if CheckHelp("iesg", Args): return + if Args[0] == "": + print("Must give at least one of 'agenda' or 'docs' as an argument; skipping.") + return + for ThisArg in Args: + # If it is just a number, check for the RFC + if ThisArg.lower() == "agenda": + WebDisplay("https://datatracker.ietf.org/iesg/agenda", "") + if ThisArg.lower() == "docs": + WebDisplay("https://datatracker.ietf.org/iesg/agenda/documents", "") + +### mirror -- Update the local mirror +def Cmd_mirror(Args): + if CheckHelp("mirror", Args): return + # See if the main directory exists; if not, try to create it + if pathexists(expanduser(MirrorDir)) == False: + try: + mkdir(expanduser(MirrorDir)) + except: + exit("The mirror directory '" + MirrorDir + "' does not exist, and could not be created. Exiting.") + if pathexists(expanduser(IDDir)) == False: + print("This appears to be the first time you are running this; it may take a long") + print(" time. Each mirror section will be named, but the files being mirrored will") + print(" only appear when the full directory has been mirrored; this can take hours,") + print(" depending on network speed. You can check the progress by looking in the") + print(" created directories.") + # Set up the log file + LogFile = expanduser(MirrorDir + "/mirror-log.txt") + try: + logf = open(LogFile, "a") + except: + exit("Could not open " + LogFile + " for appending. Exiting.\n") + # Print out to both the console and log file + def PrintLog(String): + print(String) + print(String, file=logf) + PrintLog("\nMirror began at " + strftime("%Y-%m-%d %H:%M:%S") + "\n") + # AllActions is the set of actions to be performed + # First see if it was already defined in the config file + if "AllActions" in globals(): + AllActions = globals()["AllActions"] + else: + AllActions = [ + [ "Internet Drafts", "rsync -avz --exclude='*.xml' --exclude='*.pdf' --exclude='*.p7s' " + + " --exclude='*.ps' --delete-after rsync.ietf.org::internet-drafts " + IDDir ], + [ "IANA", "rsync -avz --delete-after rsync.ietf.org::everything-ftp/iana/ " + IANADir ], + [ "IESG", "rsync -avz --delete-after rsync.ietf.org::iesg-minutes/ " + IESGDir ], + [ "IETF", "rsync -avz --delete-after --exclude='ipr/' " + + "ietf.org::everything-ftp/ietf/ " + IETFDir ], + [ "charters", "rsync -avz --delete-after rsync.ietf.org::everything-ftp/charter/ " + CharterDir ], + [ "conflict reviews", "rsync -avz --delete-after rsync.ietf.org::everything-ftp/conflict-reviews/ " + ConflictDir ], + [ "status changes", "rsync -avz --delete-after rsync.ietf.org::everything-ftp/status-changes/ " + StatusDir ], + [ "RFCs", "rsync -avz --delete-after " + + " --exclude='tar*' --exclude='search*' --exclude='PDF-RFC*' " + + " --exclude='tst/' --exclude='pdfrfc/' --exclude='internet-drafts/' " + + " --exclude='ien/' ftp.rfc-editor.org::everything-ftp/in-notes/ " + RFCDir ] + ] + for DoThis in AllActions: + PrintLog("Starting " + DoThis[0]) + FullOut = [] + p = Popen(DoThis[1], bufsize=-1, shell=True, stdout=PIPE) + while p.poll() is None: + FullOut.append(p.stdout.readline()) + TheOut = "" + for ThisLine in FullOut: + # Need the following to prevent printing and parsing problems later + ThisLine = ThisLine.decode("ascii") + if ThisLine.startswith("receiving "): continue + if ThisLine.startswith("sent "): continue + if ThisLine.startswith("total "): continue + if ThisLine.startswith("skipping non-regular file "): continue + if ThisLine.endswith('.listing" [1]\n'): continue + if ThisLine == "\n": continue + TheOut += ThisLine + PrintLog(TheOut) + + # Do the filling of the short-name directory + PrintLog("Filling short-name directory") + FullIDDir = expanduser(IDDir) + FullShortIDDir = expanduser(ShortIDDir) + # See if the directory mirrorded from the IETF exists and get the list of drafts + if pathexists(FullIDDir) == False: + exit("The directory with the drafts, " + IDDir + ", does not exist. Exiting.") + elif isdir(FullIDDir) == False: + exit(IDDir + "is not a directory. Exiting.") + try: + chdir(FullIDDir) + except: + exit("Weird: could not chdir to " + IDDir + ". Exiting.") + # Note that this is only making short names for .txt files, not any of the others + TheIDs = sorted(glob("draft-*.txt")) + # See if the directory to be copied to exists; if so, delete all the files there + if pathexists(FullShortIDDir) == False: + try: + mkdir(FullShortIDDir) + except: + exit("The directory where the shorter-named drafts will go, " + ShortIDDir + ", could not be created. Exiting.") + elif isdir(FullShortIDDir) == False: + exit(ShortIDDir + "is not a directory. Exiting.") + try: + chdir(FullShortIDDir) + except: + exit("Weird: could not chdir to " + ShortIDDir + ". Exiting.") + for ToDel in glob("*"): + if isdir(ToDel): + exit("Found a directory in " + ShortIDDir + ". Exiting.") + unlink(ToDel) + # Determine the shorter name and link the file with the destination + for ThisDraftName in TheIDs: + # Strip off "-nn.txt" + ShorterName = ThisDraftName[:-7] + # Test if the shorter name already exists; if so, nuke it + # This is based on the the assumption that there are two drafts where the version numbers + # are different, and because this is sorted, the higher ones should come later. + if pathexists(pathjoin(FullShortIDDir, ShorterName)): + unlink(pathjoin(FullShortIDDir, ShorterName)) + try: + link(pathjoin(FullIDDir, ThisDraftName), pathjoin(FullShortIDDir, ShorterName)) + except OSError as e: + print("For '" + ThisDraftName + "', got error: " + str(e) + ". Skipping.") + + # Make the RFC status database to make rfc status searching faster + PrintLog("Making the RFC status index") + TagBase = "{http://www.rfc-editor.org/rfc-index}" + try: + ParsedRFCDB = ElTree.parse(pathjoin(FullRFCDir, "rfc-index.xml")) + except: + exit("Weird: could not find '" + pathjoin(FullRFCDir, "rfc-index.xml") + "' when building the status index. Exiting.") + TreeRoot = ParsedRFCDB.getroot() + RFCStatus = {} + def StripLeadingZeros(InStr): + return(sub(RFCZerosPat, "\\1", InStr)) + LookForFields = ("obsoleted-by", "updated-by", "obsoletes", "updates", "is-also") + for ThisTopNode in TreeRoot: + # Just get the RFCs, not (yet) BCPs, STDs, and so on; maybe add them later + if ThisTopNode.tag == TagBase + "rfc-entry": + ThisRFCNum = StripLeadingZeros(ThisTopNode.find(TagBase + "doc-id").text.replace("RFC", "")) + RFCStatus[ThisRFCNum] = {} + for ThisLookedFor in LookForFields: + ### if ((ThisRFCNum == "2822") and (ThisLookedFor == "updated-by")): trace() + if ThisTopNode.findall(TagBase + ThisLookedFor): + RFCStatus[ThisRFCNum][ThisLookedFor] = [] + for ThisFoundOuterElement in ThisTopNode.findall(TagBase + ThisLookedFor): + for ThisFoundInnerElement in ThisFoundOuterElement.findall(TagBase + "doc-id"): + RFCStatus[ThisRFCNum][ThisLookedFor].append(StripLeadingZeros(ThisFoundInnerElement.text.replace("RFC", ""))) + if ThisTopNode.findall(TagBase + "errata-url"): + RFCStatus[ThisRFCNum]["errata"] = True + ThisTitle = ThisTopNode.find(TagBase + "title").text + if ThisTitle: + RFCStatus[ThisRFCNum]["title"] = ThisTitle + CurrStat = ThisTopNode.find(TagBase + "current-status").text + if (CurrStat and CurrStat != "UNKNOWN"): + RFCStatus[ThisRFCNum]["current-status"] = CurrStat + RFCStatus[ThisRFCNum]["authors"] = [] + for ThisFoundOuterAuthor in ThisTopNode.findall(TagBase + "author"): + for ThisFoundInnerAuthor in ThisFoundOuterAuthor.findall(TagBase + "name"): + RFCStatus[ThisRFCNum]["authors"].append(ThisFoundInnerAuthor.text) + try: + with open(RFCStatusFileLoc, mode="wb") as statusf: + jsondump(RFCStatus, statusf) + except: + exit("Could not dump status info to '" + RFCStatusFileLoc + "'. Exiting.") + + # Make the I-D status database to make rfc status searching faster + PrintLog("Making the ID status index") + try: + AllIDStatusLines = open(FullIDDir + "/all_id2.txt", mode="r").readlines() + except: + exit("Weird: could not read all_id2.txt to make the I-D status database. Exiting.") + IDStatus = {} + for ThisLine in AllIDStatusLines: + if ThisLine[0] == "#": continue + TheFields = ThisLine.split("\t") + # The key is the draft name minus the "-nn" + IDStatus[TheFields[0][0:-3]] = { \ + "status": TheFields[2], \ + "iesg-state": TheFields[3], \ + "became-rfc": TheFields[4], \ + "replaced-by": TheFields[5], \ + "last-revised": TheFields[6], \ + "wg-name": TheFields[7], \ + "area-name": TheFields[8], \ + "ad-name": TheFields[9], \ + "intended-level": TheFields[10], \ + "last-call-ends": TheFields[11], \ + "file-types": TheFields[12], \ + "title": TheFields[13], \ + "authors": TheFields[14].rstrip() } + try: + with open(IDStatusFileLoc, mode="wb") as statusf: + jsondump(IDStatus, statusf) + except: + exit("Could not dump status info to '" + IDStatusFileLoc + "'. Exiting.") + + # Finish up + PrintLog("\nMirror ended at " + strftime("%Y-%m-%d %H:%M:%S")) + logf.close() + +### rfc -- Open RFCs locally +def Cmd_rfc(Args): + if CheckHelp("rfc", Args): return + if Args[0] == "": + print("Must give at least one RFC name or number; skipping.") + return + for ThisArg in Args: + # Special case: 'index' returns the rfc-index.txt file + if ThisArg == "index": + system(DisplayTextCommand + pathjoin(FullRFCDir, "rfc-index.txt")) + continue + # Look for different ways they may have specified it + RFCTests = [ ThisArg, ThisArg + ".txt", "rfc" + ThisArg, "rfc" + ThisArg + ".txt" ] + FoundRFC = False + for ThisTest in RFCTests: + # Also check in the AUTH48 directory + for WhichDir in (FullRFCDir, FullRFCDir + "/authors"): + if pathexists(pathjoin(WhichDir, ThisTest)): + FoundRFC = True + system(DisplayTextCommand + pathjoin(WhichDir, ThisTest)) + break + if FoundRFC == False: + print("Could not find an RFC for '" + ThisArg + "' in '" + FullRFCDir + "'; skipping.") + +### rfcextra -- Open RFCs locally and also open related RFCs (updates, obsoleted, errata...) +def Cmd_rfcextra(Args): + if CheckHelp("rfcextra", Args): return + if Args[0] == "": + print("Must give at least one RFC name or number; skipping.") + return + # Open the status database before going through the arguments + try: + with open(RFCStatusFileLoc, mode="r") as statusf: + RFCStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the RFC status database, '" + RFCStatusFileLoc + "'. Exiting.") + for ThisArg in Args: + # First try to open the RFC itself + Cmd_rfc([ThisArg]) + # Then get the status of the RFC and open RFCs and errata that happened later + ThisRFCStatus = RFCStatusDB.get(ThisArg) + # If the status exists for this RFC, display additional information and open what was found + if ThisRFCStatus: + if ThisRFCStatus.get("obsoleted-by"): + for ThisObsoleted in ThisRFCStatus.get("obsoleted-by"): + print("RFC " + ThisArg + " was obsoleted by RFC " + ThisObsoleted) + Cmd_rfcextra([ThisObsoleted]) + if ThisRFCStatus.get("updated-by"): + for ThisUpdated in ThisRFCStatus.get("updated-by"): + print("RFC " + ThisArg + " was updated by RFC " + ThisUpdated) + Cmd_rfcextra([ThisUpdated]) + if ThisRFCStatus.get("errata") == True: + print("RFC " + ThisArg + " has errata") + WebDisplay("https://www.rfc-editor.org/errata_search.php?rfc=", ThisArg) + +### rfcinfo -- Show RFC information on the RFC Editor site +def Cmd_rfcinfo(Args): + if CheckHelp("rfcinfo", Args): return + if Args[0] == "": + print("Must give at least one RFC number; skipping.") + return + for ThisArg in Args: + # If it is just a number, check for the RFC + if ThisArg.isdigit(): + WebDisplay("https://www.rfc-editor.org/info/rfc", ThisArg) + # If it starts with "rfc" and rest are digits, it is also an RFC + elif (ThisArg.startswith("rfc") and ThisArg[3:].isdigit()): + WebDisplay("https://www.rfc-editor.org/info/", ThisArg) + else: + print("This command is for finding RFCs on the RFC Editor's site web site.\n") + +### rfcstatus -- Show RFC status from the database without opening the file +def Cmd_rfcstatus(Args): + if CheckHelp("rfcstatus", Args): return + if Args[0] == "": + print("Must give at least one RFC name or number; skipping.") + return + # Open the status database before going through the arguments + try: + with open(RFCStatusFileLoc, mode="r") as statusf: + RFCStatusDB = jsonload(statusf) + except: + exit("Weird: could not get data from the RFC status database, '" + RFCStatusFileLoc + "'. Exiting.") + for ThisArg in Args: + # Get the status of the RFC + ShorterArg = sub("^rfc", "", ThisArg) + ShorterArg = sub(".txt%", "", ShorterArg) + ThisRFCStatus = RFCStatusDB.get(ShorterArg) + # Be sure the status exists + if ThisRFCStatus: + print("RFC " + ShorterArg + ":") + if ThisRFCStatus.get("is-also"): + print(" Is also " + " ".join(ThisRFCStatus.get("is-also"))) + if ThisRFCStatus.get("obsoleted-by"): + print(" Obsoleted by " + " ".join(ThisRFCStatus.get("obsoleted-by"))) + if ThisRFCStatus.get("obsoletes"): + print(" Obsoletes " + " ".join(ThisRFCStatus.get("obsoletes"))) + if ThisRFCStatus.get("updated-by"): + print(" Updated by " + " ".join(sorted(ThisRFCStatus.get("updated-by")))) + if ThisRFCStatus.get("updates"): + print(" Updates " + " ".join(sorted(ThisRFCStatus.get("updates")))) + if ThisRFCStatus.get("errata") == True: + print(" Has errata") + else: + print("Weird: did not find status in the database for RFC " + ThisArg + "; skipping.") + +### tools -- Show RFCs, WGs, and drafts on the IETF Tools site +def Cmd_tools(Args): + if CheckHelp("tools", Args): return + if Args[0] == "": + print("Must give at least one RFC, WG, or draft name; skipping.") + return + for ThisArg in Args: + # If it is just a number, check for the RFC + if ThisArg.isdigit(): + WebDisplay("https://tools.ietf.org/html/rfc", ThisArg) + # If it starts with "rfc" and rest are digits, it is also an RFC + elif (ThisArg.startswith("rfc") and ThisArg[3:].isdigit()): + WebDisplay("https:tools.ietf.org/html/", ThisArg) + # If it isn't an RFC and it has no hyphens, assume it is a WG + elif ThisArg.find("-") == -1: + WebDisplay("https:tools.ietf.org/wg/", ThisArg) + # Otherwise, assume it is a draft; this might get a 404 + elif ThisArg.startswith("draft-"): + WebDisplay("https:tools.ietf.org/html/", ThisArg) + else: + print("This command is for finding RFCs, WGs (with no hypens) or drafts\n(that start with 'draft-')" \ + + " on the IETF Tools web site.\n") + +### tracker -- Show WGs and draft statuses on the Datatracker +def Cmd_tracker(Args): + if CheckHelp("tracker", Args): return + if Args[0] == "": + print("Must give at least one WG or draft name; skipping.") + return + for ThisArg in Args: + # If it is just a number, check for the RFC + if ThisArg.isdigit(): + WebDisplay("https://datatracker.ietf.org/doc/rfc", ThisArg) + # If it starts with "rfc" and rest are digits, it is also an RFC + elif (ThisArg.startswith("rfc") and ThisArg[3:].isdigit()): + WebDisplay("https://datatracker.ietf.org/doc/", ThisArg) + # If it isn't an RFC and it has no hyphens, assume it is a WG + elif ThisArg.find("-") == -1: + WebDisplay("https://datatracker.ietf.org/wg/", ThisArg) + # If not, assume it is a draft + elif ThisArg.startswith("draft-"): # This might get a 404 + WebDisplay("https://datatracker.ietf.org/doc/", ThisArg) + else: + print("This command is for finding WGs (with no hypens) or drafts (that start with 'draft-')" \ + + " on the IETF Datatracker.\n") + +# For showing help when --help or -h is given on the command line +def ShowCommandLineHelp(ignore1, ignore2, ignore3, ignore4): + CheckHelp("allshellcmds", "__helptext__") + exit() + +########## +# The real program starts here +########## + +Parse = OptionParser(add_help_option=False, usage="Something here") +# Don't display tombstones unless option is given +Parse.add_option("--tombstones", action="store_true", dest="DisplayTombstones", default=False) +# Maximum number of drafts to display +Parse.add_option("--maxdrafts", action="store", type="int", dest="MaxDrafts", default=10) +# Only open drafts from directory with full draft names (including version numbers) +Parse.add_option("--usedraftnumbers", action="store_true", dest="UseDraftNumbers", default=False) +# Normally have the "draft" and "rfc" commands be verbose +Parse.add_option("--quiet", action="store_true", dest="QuietDraft", default=False) +# Set up the help +Parse.add_option("--help", "-h", action="callback", callback=ShowCommandLineHelp) +(Opts, RestOfArgs) = Parse.parse_args() +# Define these top-level variables to make it easier to change them from the config file +DisplayTombstones = Opts.DisplayTombstones +MaxDrafts = Opts.MaxDrafts +UseDraftNumbers = Opts.UseDraftNumbers +QuietDraft = Opts.QuietDraft + +ConfigFile = "" +for ThisPlace in ConfigPlaces: + if pathexists(expanduser(ThisPlace)): + ConfigFile = ThisPlace + break +if ConfigFile == "": + exit("Could not find a configuration file in " + " or ".join(ConfigPlaces) + "\nExiting.") + +# Get the variable names for the directories and display mechanisms +try: + Configs = open(expanduser(ConfigFile), mode="r").read() +except: + exit("Could not open '" + expanduser(ConfigFile) + "' for input. Exiting.") +try: + exec(Configs) +except: + exit("Failed during exec of " + ConfigFile + ". Exiting.") + +# All the variables from the config file must be defined, and the named directories must exist. +TheDirectories = ( "MirrorDir", "IDDir", "ShortIDDir", "IANADir", "IESGDir", "IETFDir", "RFCDir" ) +for ThisDir in TheDirectories: + if not ThisDir in dir(): + exit("The variable '" + ThisDir + "' was not defined in " + ConfigFile + ". Exiting.") + globals()["Full" + ThisDir] = expanduser(globals()[ThisDir]) + if not(pathexists(globals()["Full" + ThisDir])): + print("The directory '" + globals()["Full" + ThisDir] + "' does not exist.\n" \ + + "You need to run the 'ietf mirror' command before running any other command.\n") +# The display mechanisms can be blank +# Set defaults for the desplay commands if they are not set +if DisplayTextCommand == "": + # If DisplayTextCommand is not set but the EDITOR environment variable is, use EDITOR instead + if environ.get("EDITOR", "") != "": + DisplayTextCommand = environ["EDITOR"] + " " + else: + DisplayTextCommand = "less " +if DisplayWebCommand == "": + DisplayWebCommand = "less " # This is a terrible fallback, of course + +# Location of the RFC and JSON files (which could not be complete until we got the config) +RFCStatusFileLoc = pathjoin(FullRFCDir, "ietf-rfc-status.json") +IDStatusFileLoc = pathjoin(FullIDDir, "ietf-id-status.json") + +# The "ietf" command can be called with no arguments to go to the internal command processor +# It is often called as "ietf" with arguments from the KnownCommand list. +if RestOfArgs == []: + FromCommandLine = False + try: + OurCLI().cmdloop() + except KeyboardInterrupt: + exit("\n^C caught. Exiting.") +else: + FromCommandLine = True + GivenCmd = RestOfArgs[0] + if GivenCmd in KnownCmds: + globals()["Cmd_" + GivenCmd](RestOfArgs[1:]) + else: + exit("Found a bad command: " + GivenCmd + ". Exiting.") diff --git a/ietf.README b/ietf.README new file mode 100644 index 000000000000..f15f084bf9df --- /dev/null +++ b/ietf.README @@ -0,0 +1,85 @@ +=========================================================
+General Instructions
+
+The "ietf" program lets you access IETF-related files from the command line.
+It creates a local copy of these files on your computer using rsync, and gives
+a friendly way to access them. You can give commands from your normal shell,
+or you can run an interactive shell that is part of the program.
+
+These instructions assume that you are using a Unix-based system (including
+MacOS), and are already comfortable with the command-line interfaces of your
+shell. There are instructions for Windows users later in this document.
+
+The "ietf" program requires Python 2.6 or later; that should be available on
+almost any modern computing platform.
+
+The program consists of two files: "ietf" (the executable) and "ietf.config"
+(mandatory configuration settings). The latter file must be either in one of
+the following directories: ~/bin/, /usr/local/bin/, or ~/.ietf/. The
+executable can be anywhere, but is probably best kept with the configuration
+file.
+
+Make the "ietf" file executable with "chmod u+x ietf".
+
+You can make aliases in your shell for the program if you normally run it from
+the shell command line to save keystrokes. For example, if you use the bash
+shell, you might make an alias of:
+ alias charter='ietf charter '
+
+Edit the ietf.config with an editor. There is a description of the values at
+the top of that file. It is likely you will want to change the value of
+DisplayTextCommand to be your favorite text editor and the value of the
+DisplayWebCommand to be the executable for your favorite web browser.
+
+=========================================================
+Running the Program
+
+There are two ways to run the "ietf" program: in its own command line
+processor, or from your normal shell. For the former, just give the command
+"ietf", and you will see the "ietf: " prompt. For the latter, give all the
+arugments necessary after the "ietf" command itself. For example, to see the
+charter of the TLS Working Group:
+
+- In the command line processor, give the command "charter tls" at the
+"ietf:" prompt
+
+- From your shell, give the command "ietf charter tls"
+
+There are many commands available; see the help text for a complete list and
+description.
+
+The first run of the program, you need to fill the local mirror with the
+"mirror" command in the command line processor or "ietf mirror" from your
+shell. This takes about 40 minutes on a low-end broadband connection.
+
+=========================================================
+Instructions for Windows Users
+
+These instructions assume that you are already comfortable with command-line
+interfaces, both Windows-style and Unix-style.
+
+Do a standard base install of Cygwin; instructions are on the cygwin.com site.
+
+Start the Cygwin installer again, and install "python" from the Python
+section, "rsync" from the Net section, and an editor from the Editors section
+("nano" is a very easy one to learn if you aren't comfortable with vi or
+emacs).
+
+Open a Windows "command" window (not the Cygwin shell). Give the command
+"\cygwin\bin\ash". "ash" prints a "$" prompt; give the command
+"/bin/rebaseall". Give the "exit" command to leave the "ash" program and close
+the Windows "command" window. (This step is a bit of magic to fix a bug
+involving python in Cygwin.)
+
+Start the Cygwin shell program. Create a "bin" directory in your home
+directory, and put the two files ("ietf" and "ietf.config") there.
+
+When editing ietf.config file, it is likely you will want to change the
+DisplayTextCommand and DisplayWebCommand values. For example, if you installed
+"nano" under Cygwin and have installed Firefox under Windows, you might use:
+ DisplayTextCommand = "nano "
+ DisplayWebCommand = "C:'\Program Files\Mozilla Firefox'\firefox "
+Or, if you want to use the Windows Notepad program for viewing text files:
+ DisplayTextCommand = "/cygdrive/c/Windows/System32/notepad.exe "
+
+You can now follow the directions above for running the "ietf" command.
diff --git a/ietf.README.sig b/ietf.README.sig Binary files differnew file mode 100644 index 000000000000..3fb59d4a6eed --- /dev/null +++ b/ietf.README.sig diff --git a/ietf.config b/ietf.config new file mode 100644 index 000000000000..7d026ec7c44c --- /dev/null +++ b/ietf.config @@ -0,0 +1,52 @@ +# Configuration file for the "ietf" program +__version__ = "1.14" +__license__ = "http://en.wikipedia.org/wiki/WTFPL" + +# The lines in this file need to look like the following +# (minus the # comment character) +# MirrorDir is the base directory for the mirrored files; +# the rest are subdirectories where mirrored files go +# This can start with a "~" to indicate your home directory or +# use a full path name +# DisplayTextCommand is the command used to display files found +# in local searches (the "draft" and "rfc" commands) +# If DisplayTextCommand is not set, but EDITOR is set in your +# Unix environment, EDITOR will be used. +# DisplayWebCommand is the command used to launch URLs in remote +# commands (the "tools" and "tracker" commands). On Macintosh +# computers, using "open " for the DisplayWebCommand usually +# works well. +# +# MirrorDir = "~/MyMirrors" +# IDDir = MirrorDir + "/id" +# ShortIDDir = MirrorDir + "/short-id" +# IANADir = MirrorDir + "/iana" +# IESGDir = MirrorDir + "/iesg-minutes" +# IETFDir = MirrorDir + "/ietf" +# CharterDir = MirrorDir + "/charter" +# ConflictDir = MirrorDir = "/conflict-reviews" +# StatusDir = MirrorDir = "/status-changes" +# RFCDir = MirrorDir + "/in-notes" +# DisplayTextCommand = "emacs " +# DisplayWebCommand = "somebrowser " + +MirrorDir = "~/LocalMirror" +IDDir = MirrorDir + "/id" +ShortIDDir = MirrorDir + "/short-id" +IANADir = MirrorDir + "/iana" +IESGDir = MirrorDir + "/iesg-minutes" +IETFDir = MirrorDir + "/ietf" +CharterDir = MirrorDir + "/charter" +ConflictDir = MirrorDir + "/conflict-reviews" +StatusDir = MirrorDir + "/status-changes" +RFCDir = MirrorDir + "/in-notes" +DisplayTextCommand = "bbedit " +DisplayWebCommand = "open " + +# Advanced users: if you want to change which directories are mirrored +# by the "mirror" command, you can define actions here to replace +# the one in the main program. You can also set defaults for the +# following variables instead of using command-line arguments: +# DisplayTombstones (default is False) +# MaxTombstones (default is 10) +# UseDraftNumbers (default is False) diff --git a/ietf.config.arch b/ietf.config.arch new file mode 100644 index 000000000000..d3277d085a9b --- /dev/null +++ b/ietf.config.arch @@ -0,0 +1,52 @@ +# Configuration file for the "ietf" program +__version__ = "1.14" +__license__ = "http://en.wikipedia.org/wiki/WTFPL" + +# The lines in this file need to look like the following +# (minus the # comment character) +# MirrorDir is the base directory for the mirrored files; +# the rest are subdirectories where mirrored files go +# This can start with a "~" to indicate your home directory or +# use a full path name +# DisplayTextCommand is the command used to display files found +# in local searches (the "draft" and "rfc" commands) +# If DisplayTextCommand is not set, but EDITOR is set in your +# Unix environment, EDITOR will be used. +# DisplayWebCommand is the command used to launch URLs in remote +# commands (the "tools" and "tracker" commands). On Macintosh +# computers, using "open " for the DisplayWebCommand usually +# works well. +# +# MirrorDir = "~/MyMirrors" +# IDDir = MirrorDir + "/id" +# ShortIDDir = MirrorDir + "/short-id" +# IANADir = MirrorDir + "/iana" +# IESGDir = MirrorDir + "/iesg-minutes" +# IETFDir = MirrorDir + "/ietf" +# CharterDir = MirrorDir + "/charter" +# ConflictDir = MirrorDir = "/conflict-reviews" +# StatusDir = MirrorDir = "/status-changes" +# RFCDir = MirrorDir + "/in-notes" +# DisplayTextCommand = "emacs " +# DisplayWebCommand = "somebrowser " + +MirrorDir = "~/.ietfmirror" +IDDir = MirrorDir + "/id" +ShortIDDir = MirrorDir + "/short-id" +IANADir = MirrorDir + "/iana" +IESGDir = MirrorDir + "/iesg-minutes" +IETFDir = MirrorDir + "/ietf" +CharterDir = MirrorDir + "/charter" +ConflictDir = MirrorDir + "/conflict-reviews" +StatusDir = MirrorDir + "/status-changes" +RFCDir = MirrorDir + "/in-notes" +DisplayTextCommand = "vi " +DisplayWebCommand = "lynx " + +# Advanced users: if you want to change which directories are mirrored +# by the "mirror" command, you can define actions here to replace +# the one in the main program. You can also set defaults for the +# following variables instead of using command-line arguments: +# DisplayTombstones (default is False) +# MaxTombstones (default is 10) +# UseDraftNumbers (default is False) diff --git a/ietf.config.arch.sig b/ietf.config.arch.sig Binary files differnew file mode 100644 index 000000000000..17d67dae52b2 --- /dev/null +++ b/ietf.config.arch.sig diff --git a/ietf.config.sig b/ietf.config.sig Binary files differnew file mode 100644 index 000000000000..1b0f2bf693bc --- /dev/null +++ b/ietf.config.sig |