#!/bin/bash
#
# rfc
#
# Author: Baptiste Fontaine
# Co-Authors: see https://github.com/bfontaine/rfc/graphs/contributors?all=1
# License: MIT
# Version: 2.0.1 (2026/08/04)
#
# URL: https://github.com/bfontaine/rfc
#

__rfc() {

    local VERSION='2.0.1'
    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
    local NO_RSYNC=5

    # URLs
    local REPO_HOME='https://github.com/bfontaine/rfc'
    local ISSUES_URL='https://github.com/bfontaine/rfc/issues'

    local RFCS_RSYNC_URL='rsync.rfc-editor.org::rfcs-text-only'
    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_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() {
      (
        cd "$rfc_dir"
        \ls -1 | grep -v '^_' | sort -n | xargs awk '
          /[A-Za-z]/ && !prev    { i++ }
          /[A-Za-z]/ && i == 2   { title = title " " $0 }
          /[A-Za-z]/ && i > 2    { n = split(FILENAME, a, "/")
                                   gsub(" +", " ", title)
                                   gsub("^ *", "", title)
                                   if (int(a[n]))
                                     printf "RFC %5d: %s\n", a[n], title
                                   else
                                     printf "RFC %s: %s\n", a[n], title
                                   i = 0
                                   title = ""
                                   nextfile }
                                 { prev = $0 }'
        )
    }

    # rfc search <pattern>
    search() {
        if [ -z "$1" ]; then
            echo 'Usage: rfc search "<pattern>"'
            return $UNRECOGNIZED_CMD;
        fi
        grep -RIs --exclude _404s $@ "$rfc_dir" | sed "s%$rfc_dir/%RFC %"
        return 0
    }

    # rfc sync
    # shellcheck disable=SC2164
    sync_rfcs() {
        if [ -n "$1" ]; then
            echo "'sync' doesn't take any argument." >&2
            return $UNRECOGNIZED_CMD
        fi

        if ! which rsync > /dev/null 2>&1; then
            echo "Error: 'rfc sync' needs rsync!" >&2
            return $NO_RSYNC
        fi

        # --info=progress2 (rsync >= 3.1) shows a single overall progress
        # line; older rsyncs fall back to listing the transferred files.
        local progress='-v'
        if rsync --info=help > /dev/null 2>&1; then
            progress='--info=progress2'
        fi

        mkdir -p "$rfc_dir/_tmp"
        if ! rsync -az "$progress" --include='rfc[0-9]*.txt' --exclude='*' \
            "$RFCS_RSYNC_URL/" "$rfc_dir/_tmp/"; then
          rm -rf "$rfc_dir/_tmp"
          return $NETWORK_ERROR
        fi
        echo "Moving the RFCs into the cache; this can take a minute..."
        cd "$rfc_dir/_tmp"
        # The regex must be in a variable: bash < 3.2 can't parse it inline.
        local rfc_txt_re='^rfc([0-9]+)\.txt$'
        for f in rfc*.txt; do
            if [[ "$f" =~ $rfc_txt_re ]]; then
                mv -f "$f" "$rfc_dir/${BASH_REMATCH[1]}"
            fi
        done
        rm -rf "$rfc_dir/_tmp"
        echo "Done."
    }

    # 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 <number>              # display the RFC <number>
    rfc search [OPTS] X       # Search for X in local RFCs using grep with OPTS
                                passed through
    rfc list                  # List locally available RFCs
    rfc sync                  # batch download all RFCs using rsync. 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 "$@"

