#! /bin/sh -
#
# moinmoincli - Edit existing MoinMoin wiki pages from a command line.
#
#               This script has been tested only on the FreeBSD wiki
#               (https://wiki.freebsd.org).
#
# ---
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2017, 2018 Mateusz Piotrowski <0mp@FreeBSD.org>
#
# 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.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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.

version=4.6.0

base='https://wiki.freebsd.org'
conffile="$HOME/.moinmoincli.conf"
workdir="$(env -u TMPDIR mktemp -d -t moinmoincli)"
[ "$?" -ne 0 ] && errxit "failed to create a work directory"
editfile="$workdir/edit.html"
oldrawfile="$workdir/oldraw.txt"
cookiefile="$workdir/cookie.txt"

usage() {
    cat << 'EOF'
Usage: moinmoincli [-hquv] [-f textfile] [-n username] [-t target] [selector]
EOF
}

# $1 -- field number
# $2 -- field separator
# $3 -- regex
# $editfile -- parsed file
take() {
    grep -E -o "$3" "$editfile" | cut -d "$2" -f "$1"
}

# $1 -- text to be add to $data (& between fields is added automatically)
# $data
add_data() {
    data="$data${data:+&}$1"
}

# $textfile
# $oldrawfile
show_diff() {
    if which git > /dev/null 2>&1
    then
        if git diff --exit-code --color=always -- "$oldrawfile" "$textfile"
        then
            echo no changes
            exit 1
        else
            echo
        fi
    else
        if diff -- "$oldrawfile" "$textfile"
        then
            echo no changes
            exit 1
        else
            echo
        fi
    fi
}

# $comment
get_comment() {
    printf 'Comment: '
    read -r comment
}

# Inspiration: https://stackoverflow.com/a/7506695/4694621
# $1 -- data to be encoded
encode() {
    printf '%s' "$1" | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g'
}

# $workdir
clean_up() {
    if [ -n "$workdir" ] && [ -d "$workdir" ]
    then
        rm -rf "$workdir"
    fi
    stty echo
}

# $name
# $target
# $comment
# $textfile
print_summary() {
    cat <<-EOF
Summary
=======
Username:  $name
Target:    $target
Comment:   $comment
Text file: $textfile
EOF
}

errxit() {
	printf "error: %s\n" "$*"
	exit 1
}

# This may be overwritten in the configuration file.
# $target
select_target() {
    printf '%s' "$target"
}

# $base
# $oldrawfile
# $rawpage
# $target
download_rawpage() {
    rawpage="$base/$target?action=raw"
    if ! curl --cookie "$cookiefile" "$rawpage" 1>"$oldrawfile" 2>/dev/null
    then
        errxit "failed to download old revision"
    fi
}

# $base
# $target
parse_target() {
    case $target in
        # If $target is already '/Foo' then we're done.
        /*) return ;;
        # Strip 'https://' (and HTTP) 'https://wiki.freebsd.org/Foo/'.
        http*) target="${target#http*://}" ;;
    esac
    case $target in
        # Strip 'wiki.freebsd.org' from 'wiki.freebsd.org/Foo/'.
        "${base#http*://}"*) target="${target#"${base#http*://}"}" ;;
        *) ;;
    esac
}

# $base
# $cookiefile
# $name
# $password
authenticate() {
    if [ -z "$password" ]
    then
        printf '%s password: ' "$name"
        stty -echo
        read -r password
        stty echo
    fi

    data=
    add_data "action=login"
    add_data "login=Login"
    add_data "name=$name"
    add_data "password=$password"

    loginpage="$base/action/login/FrontPage"
    if ! curl --data "$data" --cookie-jar "$cookiefile" "$loginpage" 1>/dev/null 2>&1
    then
        errxit "failed to log in"
    fi
}

trap 'clean_up' EXIT

unset -v password
[ -r "$conffile" ] && . "$conffile"

while [ $# -gt 0 ]
do
    case "$1" in
        -n|--name)
            [ $# -eq 0 ] && errxit "missing value for the '$1' flag"
            name="$2"
            shift 2
            ;;
        -f|--file)
            [ $# -eq 0 ] && errxit "missing value for the '$1' flag"
            textfile="$2"
            shift 2
            ;;
        -q|--quick)
            quickmode=yes
            shift 1
            ;;
        -t|--target)
            [ $# -eq 0 ] && errxit "missing value for the '$1' flag"
            target="$2"
            shift 2
            ;;
        -h|--help)
            shift
            usage
            exit 0
            ;;
        -u|--update)
            shift
            updatemode=yes
            ;;
        -v|--version)
            shift
            printf '%s\n' "$version"
            exit 0
            ;;
        *)
            select_target "$1"
            shift
            ;;
    esac
done

[ -z "$target" ] && errxit 'missing target'
[ -z "$textfile" ] && errxit 'missing text file'

parse_target

[ -z "$name" ] && errxit 'missing username'

if [ "@$updatemode@" = '@yes@' ]
then
    authenticate
    download_rawpage
    cat "$oldrawfile" > "$textfile"
    echo updated.
    exit 0
fi

! textarea="$(cat -- "$textfile")" && errxit "failed to open '$textfile'"

download_rawpage

show_diff
get_comment
print_summary
cat <<-EOF
actions:
  * confirm        -> y,k
  * abort          -> n,a
  * mark trivial   -> t
  * change comment -> c
  * show diff      -> d
  * print summary  -> s
EOF
while true
do
    [ "@${quickmode:-no}@" = "@yes@" ] && break
    printf '[yknatcds] '
    read -r action
    case "$action" in
        y|k)
            break
            ;;
        n|a)
            exit 1
            ;;
        t)
            trivial=yes
            echo 'marked as trivial'
            ;;
        c)
            get_comment
            ;;
        d)
            show_diff
            ;;
        s)
            print_summary
            ;;
        *)
            printf 'invalid action (%s)\n' "$action"
            ;;
    esac
done

echo processing...

authenticate

editpage="$base/action/edit$target?action=edit&editor=text"
if ! curl --cookie "$cookiefile" "$editpage" 1>"$editfile" 2>/dev/null
then
    errxit "failed to get the edit page"
fi

data=
add_data "action=edit"
add_data "rev=$(take 6 '"' '<input type="hidden" name="rev" value="[0-9]+">')"
add_data "ticket=$(take 6 '"' '<input type="hidden" name="ticket" value=".*">')"
add_data "button_save=Save Changes"
add_data "editor=text"
add_data "comment=$(encode "$comment")"
add_data "savetext=$(encode "$textarea")"
[ "@$trivial@" = "@yes@" ] && add_data "trivial=1"

if ! curl --data "$data" --cookie "$cookiefile" "$base$target#preview" 1>/dev/null 2>&1
then
    errxit "failed to update the page"
fi

echo done.
