#! /usr/bin/env bash # # rfc # # Author: Baptiste Fontaine # License: MIT # Version: 1.0.0 (2024/03/26) # # URL: https://github.com/bfontaine/rfc # __rfc() { local VERSION='1.0.0' local PAGER=${PAGER:-less} local fetch_cmd=${CURL:-curl} local rfc_dir local default_rfc_dir="$HOME/.cache/RFCs" local code # Error codes local NOT_FOUND=1 local UNRECOGNIZED_CMD=2 local NETWORK_ERROR=3 local NO_CURL_WGET=4 # URLs local REPO_HOME='https://github.com/bfontaine/rfc' local ISSUES_URL='https://github.com/bfontaine/rfc/issues' local RFCS_TGZ_BASEURL='https://www.rfc-editor.org/in-notes/tar/' local RFCS_BASEURL='https://www.rfc-editor.org/rfc/' local DRAFTS_BASEURL='https://www.ietf.org/id/' if [ -n "$RFC_DIR" ]; then rfc_dir="$RFC_DIR" elif [ -n "$XDG_CACHE_HOME" ]; then rfc_dir="$XDG_CACHE_HOME/RFCs" else rfc_dir="$default_rfc_dir" fi # $rfc_dir must be absolute [ "${rfc_dir:0:1}" != "/" ] && rfc_dir="$PWD/$rfc_dir" print_rfc() { $PAGER "$rfc_dir/$1" } fetch_url() { case $fetch_cmd in curl) curl -fs "$1" >| "$2"; case "$?" in 6|7) return $NETWORK_ERROR ;; 22) return $NOT_FOUND ;; *) return 0 ;; esac ;; wget) wget -cq "$1" -O "$2"; case "$?" in 4) return $NETWORK_ERROR ;; 8) return $NOT_FOUND ;; *) return 0 ;; esac ;; esac } get_ftp_url() { local tmp tmp=$(mktemp) # $1 = pattern of the file we're looking for local pattern="$1" local fetch_exit_code= fetch_url "$RFCS_TGZ_BASEURL" "$tmp" fetch_exit_code=$? if [ "$fetch_exit_code" -ne 0 ]; then return $fetch_exit_code fi # Lines have the following format: #
  • RFCs0001-0500.tar.gz
  • # We want this __^^^^^^^^^^^^^^^^^^^^ < "$tmp" grep "$pattern" | cut -d'"' -f2 } get_rfc() { fetch_url "$RFCS_BASEURL/rfc$1.txt" "$rfc_dir/$1" return $? } get_draft() { fetch_url "$DRAFTS_BASEURL/$1.txt" "$rfc_dir/$1" return $? } migrate_rfc_dir() { local previous_rfc_dir="$HOME/.RFCs" if [ -f "$previous_rfc_dir/_404s" ] && [ "$rfc_dir" != "$previous_rfc_dir" ]; then # Migrate _404s cat "$previous_rfc_dir/_404s" >> "$rfc_dir/_404s" rm "$previous_rfc_dir/_404s" # Migrate the RFC files mv -f "$previous_rfc_dir"/* "$rfc_dir/" echo "In 1.0.0 the RFC cache directory moved from $previous_rfc_dir to $rfc_dir." >&2 echo "All files were migrated; you can now run \`rmdir '$previous_rfc_dir'\`" >&2 fi } init_rfc_dir() { mkdir -p "$rfc_dir" touch "$rfc_dir"/_404s if [ -z "$RFC_TEST" ]; then migrate_rfc_dir fi } print_not_found_error() { if [[ "$1" == draft* ]]; then echo "There's no such draft." else echo "There's no such RFC." fi } ## Subcommands ## # rfc list list() { \ls -1 "$rfc_dir" | grep '^[0-9]\{1,\}$' | sort -n | sed 's/^/RFC /' } # rfc search search() { if [ -z "$1" ]; then echo 'Usage: rfc search ""' return $UNRECOGNIZED_CMD; fi grep -RIs --exclude _404s $@ "$rfc_dir" | sed "s%$rfc_dir/%RFC %" return 0 } # rfc sync [week|month|all] # shellcheck disable=SC2164 sync_rfcs() { local name=RFC-all.tar.gz case "$1" in month|30|30days) name=$(get_ftp_url "30daysTo.*gz") ;; week|7|7days) name=$(get_ftp_url "7daysTo.*gz") ;; all) ;; *) # Support 'sync' without any argument as an alias to 'sync all' if [ ! -z "$1" ]; then echo "Unrecognized 'sync' target: $1" return $UNRECOGNIZED_CMD fi ;; esac # shellcheck disable=SC2181 if [ $? -ne 0 ]; then return $? fi # This shouldn't happen. if [ -z "$name" ]; then echo "I couldn't find the requested archive." >&2 echo "Please report this: $ISSUES_URL" >&2 return $NOT_FOUND fi mkdir -p "$rfc_dir/_tmp" if fetch_url "$RFCS_TGZ_BASEURL$name" "$rfc_dir/_tmp/$name"; then : else exit $NETWORK_ERROR fi cd "$rfc_dir/_tmp" tar -xzf "$name" rm -f "$name" [ -d in-notes ] && cd in-notes for f in rfc*.txt; do echo "$f" | grep -q '^rfc[0-9]\{1,\}\.txt$' if [ "$?" -eq "0" ]; then n=${f##rfc}; n=${n%%.txt}; mv -f "$f" "$rfc_dir/$n" fi done rm -rf "$rfc_dir/_tmp" } # rfc help print_usage() { # shellcheck disable=SC2016 echo 'Usage: rfc --version # display the version number and exit rfc --help # display this text and exit rfc # display the RFC rfc search [OPTS] X # Search for X in local RFCs using grep with OPTS passed through rfc list # List locally available RFCs rfc sync [week|month|all] # batch download RFCs. `week` and `month` respectively download only RFCs that were added/updated during the last week/month. `all` (default) downloads all RFCs. It might take some time, be patient. rfc clear # clear the cache The default cache directory is '"$default_rfc_dir"'. The current one is '"$rfc_dir"'.' } # rfc version print_version() { echo "rfc v$VERSION - $REPO_HOME" } ## /commands ## if [ $# -eq 0 ]; then print_usage return 0 fi # Prefix everything with --debug to enable the debugging output on STDERR. if [ "$1" == "--debug" ]; then shift ; echo "Enabling debug mode." >&2 ; set -x ; fi if which curl > /dev/null 2>&1; then if which wget > /dev/null 2>&1; then fetch_cmd='wget' else echo "Error: You need Wget or cURL!" return $NO_CURL_WGET; fi fi init_rfc_dir case "$1" in -v|--version|version) print_version return 0;; -h|--help|-help|help) print_usage return 0;; -*) echo "Unrecognized option: $1" print_usage return $UNRECOGNIZED_CMD;; clear|clean|clr) rm -rf "$rfc_dir" return 0;; ls|list) list return 0;; search|find) shift; search "$@" return $?;; sync|download) shift; sync_rfcs "$@" return $?;; _*) print_usage return 1;; *) if grep -q "^$1\$" "$rfc_dir/_404s" 2> /dev/null; then print_not_found_error "$1" return $NOT_FOUND elif [ ! -f "$rfc_dir/$1" ]; then if [[ "$1" == draft* ]]; then get_draft "$1" else get_rfc "$1" fi code=$? if [ $code -eq $NETWORK_ERROR ]; then echo "Unable to connect to the network." [ ! -s "$rfc_dir/$1" ] && rm -f "$rfc_dir/$1" return $NETWORK_ERROR fi if [ $code -eq $NOT_FOUND ]; then echo "$1" >> "$rfc_dir/_404s" print_not_found_error "$1" [ ! -s "$rfc_dir/$1" ] && rm -f "$rfc_dir/$1" return $NOT_FOUND fi fi print_rfc "$@" ;; esac } __rfc "$@"