commit bd7a799f35367acf181b649c2a4a1b4a68a385ae Author: David Parsons Date: Thu Dec 20 18:20:26 2007 -0800 Check all of the bits of markdown into version control before some horrible catastrophe happens diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..c26dafa --- /dev/null +++ b/Makefile.in @@ -0,0 +1,31 @@ +CC=@CC@ +BINDIR=@exedir@ +MANDIR=@mandir@ + +PGM=markdown + +all: $(PGM) + +install: $(PGM) + @INSTALL_PROGRAM@ $(PGM) $(BINDIR) + @MKSUID@ $(BINDIR)/$(PGM) + +# install.man: +# @INSTALL_DIR@ $(MANDIR)/man1 +# @INSTALL_DATA@ pstree.1 $(MANDIR)/man1 + +$(PGM): markdown.c + $(CC) -o $(PGM) -DPROGRAM=1 markdown.c @LIBS@ + +# version.o: VERSION +# echo 'char version[] = VERSION;' > version.c +# $(CC) -DVERSION=\"`cat VERSION`\" -c version.c + +clean: + rm -f $(PGM) *.o + +distclean spotless: clean + rm -f @GENERATED_FILES@ @CONFIGURE_FILES@ + + +markdown.o: markdown.c config.h cstring.h diff --git a/configure.inc b/configure.inc new file mode 100755 index 0000000..11ce07e --- /dev/null +++ b/configure.inc @@ -0,0 +1,1376 @@ +# @(#) configure.inc 1.42@(#) +# Copyright (c) 1999-2007 David Parsons. 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. My name may not be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``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 DAVID +# PARSONS 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. +# + + +# +# this preamble code is executed when this file is sourced and it picks +# interesting things off the command line. +# +ac_default_path="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin" + +ac_standard="--src=DIR where the source lives (.) +--prefix=DIR where to install the final product (/usr/local) +--execdir=DIR where to put executables (prefix/bin) +--sbindir=DIR where to put static executables (prefix/sbin) +--confdir=DIR where to put configuration information (/etc) +--libdir=DIR where to put libraries (prefix/lib) +--libexecdir=DIR where to put private executables +--mandir=DIR where to put manpages" + +__fail=exit + +if dirname B/A 2>/dev/null >/dev/null; then +__ac_dirname() { + dirname "$1" +} +else +__ac_dirname() { + echo "$1" | sed -e 's:/[^/]*$::' +} +fi + +ac_progname=$0 +ac_configure_command= +Q=\' +for x in "$@"; do + ac_configure_command="$ac_configure_command $Q$x$Q" +done +# ac_configure_command="$*" + +__d=`__ac_dirname "$ac_progname"` +if [ "$__d" = "$ac_progname" ]; then + AC_SRCDIR=`pwd` +else + AC_SRCDIR=`cd $__d;pwd` +fi + +__ac_dir() { + if test -d "$1"; then + (cd "$1";pwd) + else + echo "$1"; + fi +} + +while [ $# -gt 0 ]; do + unset matched + + case X"$1" in + X--src|X--srcdir) + AC_SRCDIR=`__ac_dir "$2"` + _set_srcdir=1 + shift 2;; + + X--src=*|X--srcdir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_SRCDIR=`__ac_dir "$__d"` + _set_srcdir=1 + shift 1 ;; + + X--prefix) + AC_PREFIX=`__ac_dir "$2"` + _set_prefix=1 + shift 2;; + + X--prefix=*) + __d=`echo "$1"| sed -e 's/^[^=]*=//'` + AC_PREFIX=`__ac_dir "$__d"` + _set_prefix=1 + shift 1;; + + X--confdir) + AC_CONFDIR=`__ac_dir "$2"` + _set_confdir=1 + shift 2;; + + X--confdir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_CONFDIR=`__ac_dir "$__d"` + _set_confdir=1 + shift 1;; + + X--libexec|X--libexecdir) + AC_LIBEXEC=`__ac_dir "$2"` + _set_libexec=1 + shift 2;; + + X--libexec=*|X--libexecdir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_LIBEXEC=`__ac_dir "$__d"` + _set_libexec=1 + shift 1;; + + X--lib|X--libdir) + AC_LIBDIR=`__ac_dir "$2"` + _set_libdir=1 + shift 2;; + + X--lib=*|X--libdir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_LIBDIR=`__ac_dir "$__d"` + _set_libdir=1 + shift 1;; + + X--exec|X--execdir) + AC_EXECDIR=`__ac_dir "$2"` + _set_execdir=1 + shift 2;; + + X--exec=*|X--execdir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_EXECDIR=`__ac_dir "$__d"` + _set_execdir=1 + shift 1;; + + X--sbin|X--sbindir) + AC_SBINDIR=`__ac_dir "$2"` + _set_sbindir=1 + shift 2;; + + X--sbin=*|X--sbindir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_SBINDIR=`__ac_dir "$__d"` + _set_sbindir=1 + shift 1;; + + X--man|X--mandir) + AC_MANDIR=`__ac_dir "$2"` + _set_mandir=1 + shift 2;; + + X--man=*|X--mandir=*) + __d=`echo "$1" | sed -e 's/^[^=]*=//'` + AC_MANDIR=`__ac_dir "$__d"` + _set_mandir=1 + shift 1;; + + X--use-*=*) + _var=`echo "$1"| sed -n 's/^--use-\([A-Za-z][-A-Za-z0-9_]*\)=.*$/\1/p'` + if [ "$_var" ]; then + _val=`echo "$1" | sed -e 's/^--use-[^=]*=\(.*\)$/\1/'` + _v=`echo $_var | tr '[a-z]' '[A-Z]' | tr '-' '_'` + case X"$_val" in + X[Yy][Ee][Ss]|X[Tt][Rr][Uu][Ee]) eval USE_${_v}=T ;; + X[Nn][Oo]|X[Ff][Aa][Ll][Ss][Ee]) eval unset USE_${_v} ;; + *) echo "Bad value for --use-$_var ; must be yes or no" + exit 1 ;; + esac + else + echo "Bad option $1. Use --help to show options" 1>&2 + exit 1 + fi + shift 1 ;; + + X--use-*) + _var=`echo "$1"|sed -n 's/^--use-\([A-Za-z][-A-Za-z0-9_]*\)$/\1/p'` + _v=`echo $_var | tr '[a-z]' '[A-Z]' | tr '-' '_'` + eval USE_${_v}=T + shift 1;; + + X--with-*=*) + _var=`echo "$1"| sed -n 's/^--with-\([A-Za-z][-A-Za-z0-9_]*\)=.*$/\1/p'` + if [ "$_var" ]; then + _val=`echo "$1" | sed -e 's/^--with-[^=]*=\(.*\)$/\1/'` + _v=`echo $_var | tr '[a-z]' '[A-Z]' | tr '-' '_'` + eval WITH_${_v}=\"$_val\" + else + echo "Bad option $1. Use --help to show options" 1>&2 + exit 1 + fi + shift 1 ;; + + X--with-*) + _var=`echo "$1" | sed -n 's/^--with-\([A-Za-z][A-Za-z0-9_-]*\)$/\1/p'` + if [ "$_var" ]; then + _v=`echo $_var | tr '[a-z]' '[A-Z]' | tr '-' '_'` + eval WITH_${_v}=1 + else + echo "Bad option $1. Use --help to show options" 1>&2 + exit 1 + fi + shift 1 ;; + + X--help) + echo "$ac_standard" + test "$ac_help" && echo "$ac_help" + exit 0;; + + *) if [ "$LOCAL_AC_OPTIONS" ]; then + eval "$LOCAL_AC_OPTIONS" + else + ac_error=T + fi + if [ "$ac_error" ]; then + echo "Bad option $1. Use --help to show options" 1>&2 + exit 1 + fi ;; + esac +done + + +# +# echo w/o newline +# +echononl() +{ + ${ac_echo:-echo} "${@}$ac_echo_nonl" +} + +# +# log something to the terminal and to a logfile. +# +LOG () { + echo "$@" + echo "$@" 1>&5 +} + +# +# log something to the terminal without a newline, and to a logfile with +# a newline +# +LOGN () { + echononl "$@" 1>&5 + echo "$@" +} + +# +# log something to the terminal +# +TLOG () { + echo "$@" 1>&5 +} + +# +# log something to the terminal, no newline +# +TLOGN () { + echononl "$@" 1>&5 +} + + +# +# AC_CONTINUE tells configure not to bomb if something fails, but to +# continue blithely along +# +AC_CONTINUE () { + __fail="return" +} + + +# +# generate a .o file from sources +# +__MAKEDOTO() { + AC_PROG_CC + + if $AC_CC -c -o /tmp/doto$$.o "$@" $AC_LIBS 2>/tmp/doto$$.err; then + rm -f /tmp/doto$$.o /tmp/doto$$.err + TLOG " (found)" + return 0 + fi + rm -f /tmp/doto$$.o + TLOG " (not found)" + echo "test failed: command was $AC_CC -c -o /tmp/doto$$.o" "$@" $AC_LIBS + echo "output:" + cat /tmp/doto$$.err + rm -f /tmp/doto$$.err + echo "offending sources:" + for x in "$@"; do + echo "$x:" + cat $x + done + return 1 +} + + +# +# Emulate gnu autoconf's AC_CHECK_HEADERS() function +# +AC_CHECK_HEADERS () { + + echo "/* AC_CHECK_HEADERS */" > /tmp/ngc$$.c + for hdr in $*; do + echo "#include <$hdr>" >> /tmp/ngc$$.c + done + echo "main() { }" >> /tmp/ngc$$.c + + LOGN "checking for header $hdr" + + if __MAKEDOTO /tmp/ngc$$.c; then + AC_DEFINE 'HAVE_'`echo $hdr | tr 'a-z' 'A-Z' | tr './' '_'` 1 + rc=0 + else + rc=1 + fi + rm -f /tmp/ngc$$.c + return $rc +} + + +# +# emulate GNU autoconf's AC_CHECK_FUNCS function +# +AC_CHECK_FUNCS () { + AC_PROG_CC + +F=$1 +shift +rm -f /tmp/ngc$$.c + +while [ "$1" ]; do + echo "#include <$1>" >> /tmp/ngc$$.c + shift +done + + cat >> /tmp/ngc$$.c << EOF +main() +{ + + $F(); +} +EOF + + LOGN "checking for the $F function" + + if $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS; then + AC_DEFINE `echo ${2:-HAVE_$F} | tr 'a-z' 'A-Z'` 1 + TLOG " (found)" + rc=0 + else + echo "offending command was:" + cat /tmp/ngc$$.c + echo "$AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS" + TLOG " (not found)" + rc=1 + fi + rm -f /tmp/ngc$$.c /tmp/ngc$$ + return $rc +} + + +# +# check to see if some structure exists +# +# usage: AC_CHECK_STRUCT structure {include ...} +# +AC_CHECK_STRUCT () { + struct=$1 + shift + + rm -f /tmp/ngc$$.c + + for include in $*; do + echo "#include <$include>" >> /tmp/ngc$$.c + done + + cat >> /tmp/ngc$$.c << EOF +main() +{ + struct $struct foo; +} +EOF + + LOGN "checking for struct $struct" + + if __MAKEDOTO /tmp/ngc$$.c; then + AC_DEFINE HAVE_STRUCT_`echo ${struct} | tr 'a-z' 'A-Z'` + rc=0 + else + rc=1 + fi + rm -f /tmp/ngc$$.c + return $rc +} + + +# +# check to see if some type exists +# +# usage: AC_CHECK_TYPE type {include ...} +# +AC_CHECK_TYPE () { + type=$1 + shift + + rm -f /tmp/ngc$$.c + + for include in $*; do + echo "#include <$include>" >> /tmp/ngc$$.c + done + + cat >> /tmp/ngc$$.c << EOF +main() +{ + $type foo; +} +EOF + + LOGN "checking for $type type" + + if __MAKEDOTO /tmp/ngc$$.c; then + AC_DEFINE HAVE_TYPE_`echo ${type} | tr 'a-z' 'A-Z'` + rc=0 + else + rc=1 + fi + rm -f /tmp/ngc$$.c + return $rc +} + + +# +# check to see if some structure contains a field +# +# usage: AC_CHECK_FIELD structure field {include ...} +# +AC_CHECK_FIELD () { + + struct=$1 + field=$2 + shift 2 + + rm -f /tmp/ngc$$.c + + for include in $*;do + echo "#include <$include>" >> /tmp/ngc$$.c + done + + cat >> /tmp/ngc$$.c << EOF +main() +{ + struct $struct foo; + + foo.$field; +} +EOF + + LOGN "checking that struct $struct has a $field field" + + if __MAKEDOTO /tmp/ngc$$.c; then + AC_DEFINE HAVE_`echo ${struct}_$field | tr 'a-z' 'A-Z'` + rc=0 + else + rc=1 + fi + rm -f /tmp/ngc$$.c + return $rc +} + + +# +# check that the C compiler works +# +AC_PROG_CC () { + test "$AC_CC" && return 0 + + cat > /tmp/ngc$$.c << \EOF +#include +main() +{ + puts("hello, sailor"); +} +EOF + + TLOGN "checking the C compiler" + + unset AC_CFLAGS AC_LDFLAGS + + if [ "$CC" ] ; then + AC_CC="$CC" + elif [ "$WITH_PATH" ]; then + AC_CC=`acLookFor cc` + elif [ "`acLookFor cc`" ]; then + # don't specify the full path if the user is looking in their $PATH + # for a C compiler. + AC_CC=cc + fi + + # finally check for POSIX c89 + test "$AC_CC" || AC_CC=`acLookFor c89` + + if [ ! "$AC_CC" ]; then + TLOG " (no C compiler found)" + $__fail 1 + fi + echo "checking out the C compiler" + + $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c + status=$? + + TLOGN " ($AC_CC)" + if [ $status -eq 0 ]; then + TLOG " ok" + + # check that the CFLAGS and LDFLAGS aren't bogus + + unset AC_CFLAGS AC_LDFLAGS + + if [ "$CFLAGS" ]; then + test "$CFLAGS" && echo "validating CFLAGS=${CFLAGS}" + if $AC_CC $CFLAGS -o /tmp/ngc$$.o /tmp/ngc$$.c ; then + AC_CFLAGS=${CFLAGS:-"-g"} + test "$CFLAGS" && echo "CFLAGS=\"${CFLAGS}\" are okay" + elif [ "$CFLAGS" ]; then + echo "ignoring bogus CFLAGS=\"${CFLAGS}\"" + fi + else + AC_CFLAGS=-g + fi + if [ "$LDFLAGS" ]; then + test "$LDFLAGS" && echo "validating LDFLAGS=${LDFLAGS}" + if $AC_CC $LDFLAGS -o /tmp/ngc$$ /tmp/ngc$$.o; then + AC_LDFLAGS=${LDFLAGS:-"-g"} + test "$LDFLAGS" && TLOG "LDFLAGS=\"${LDFLAGS}\" are okay" + elif [ "$LDFLAGS" ]; then + TLOG "ignoring bogus LDFLAGS=\"${LDFLAGS}\"" + fi + else + AC_LDFLAGS=${CFLAGS:-"-g"} + fi + else + AC_FAIL " does not compile code properly" + fi + + AC_SUB 'CC' "$AC_CC" + + rm -f /tmp/ngc$$ /tmp/ngc$$.c /tmp/ngc$$.o + + return $status +} + + +# +# acLookFor actually looks for a program, without setting anything. +# +acLookFor () { + path=${AC_PATH:-$ac_default_path} + case "X$1" in + X-[rx]) __mode=$1 + shift + ;; + *) __mode=-x + ;; + esac + oldifs="$IFS" + for program in $*; do + IFS=":" + for x in $path; do + if [ $__mode $x/$program -a -f $x/$program ]; then + echo $x/$program + break 2 + fi + done + done + IFS="$oldifs" + unset __mode +} + + +# +# check that a program exists and set its path +# +MF_PATH_INCLUDE () { + SYM=$1; shift + + case X$1 in + X-[rx]) __mode=$1 + shift + ;; + *) unset __mode + ;; + esac + + TLOGN "looking for $1" + + DEST=`acLookFor $__mode $*` + + __sym=`echo "$SYM" | tr '[a-z]' '[A-Z]'` + if [ "$DEST" ]; then + TLOG " ($DEST)" + echo "$1 is $DEST" + AC_MAK $SYM + AC_DEFINE PATH_$__sym \""$DEST"\" + AC_SUB $__sym "$DEST" + eval CF_$SYM=$DEST + return 0 + else + #AC_SUB $__sym '' + echo "$1 is not found" + TLOG " (not found)" + return 1 + fi +} + + +# +# AC_INIT starts the ball rolling +# +# After AC_INIT, fd's 1 and 2 point to config.log +# and fd 5 points to what used to be fd 1 +# +AC_INIT () { + __config_files="config.cmd config.sub config.h config.mak config.log" + rm -f $__config_files + __cwd=`pwd` + exec 5>&1 1>$__cwd/config.log 2>&1 + AC_CONFIGURE_FOR=__AC_`echo $1 | sed -e 's/\..$//' | tr 'a-z' 'A-Z' | tr ' ' '_'`_D + + # check to see whether to use echo -n or echo ...\c + # + echo -n hello > $$ + echo world >> $$ + if grep "helloworld" $$ >/dev/null; then + ac_echo="echo -n" + echo "[echo -n] works" + else + ac_echo="echo" + echo 'hello\c' > $$ + echo 'world' >> $$ + if grep "helloworld" $$ >/dev/null; then + ac_echo_nonl='\c' + echo "[echo ...\\c] works" + fi + fi + rm -f $$ + + LOG "Configuring for [$1]" + + cat > $__cwd/config.h << EOF +/* + * configuration for $1${2:+" ($2)"}, generated `date` + * by ${LOGNAME:-`whoami`}@`hostname` + */ +#ifndef $AC_CONFIGURE_FOR +#define $AC_CONFIGURE_FOR 1 + + +EOF + + unset __share + if [ -d $AC_PREFIX/share/man ]; then + for t in 1 2 3 4 5 6 7 8 9; do + if [ -d $AC_PREFIX/share/man/man$t ]; then + __share=/share + elif [ -d $AC_PREFIX/share/man/cat$t ]; then + __share=/share + fi + done + else + __share= + fi + + if [ -d $AC_PREFIX/libexec ]; then + __libexec=libexec + else + __libexec=lib + fi + + + AC_PREFIX=${AC_PREFIX:-/usr/local} + AC_EXECDIR=${AC_EXECDIR:-$AC_PREFIX/bin} + AC_SBINDIR=${AC_SBINDIR:-$AC_PREFIX/sbin} + AC_LIBDIR=${AC_LIBDIR:-$AC_PREFIX/lib} + AC_MANDIR=${AC_MANDIR:-$AC_PREFIX$__share/man} + AC_LIBEXEC=${AC_LIBEXEC:-$AC_PREFIX/$__libexec} + AC_CONFDIR=${AC_CONFDIR:-/etc} + + AC_PATH=${WITH_PATH:-$PATH} + AC_PROG_CPP + AC_PROG_INSTALL + + ac_os=`uname -s` + _os=`echo $ac_os | tr '[a-z]' '[A-Z]'` + AC_DEFINE OS_$_os 1 + eval OS_${_os}=1 + unset _os +} + + +# +# AC_LIBRARY checks to see if a given library exists and contains the +# given function. +# usage: AC_LIBRARY function library [alternate ...] +# +AC_LIBRARY() { + SRC=$1 + shift + + __acllibs= + __aclhdrs= + + for x in "$@"; do + case X"$x" in + X-l*) __acllibs="$__acllibs $x" ;; + *) __aclhdrs="$__aclhdrs $x" ;; + esac + done + + # first see if the function can be found in any of the + # current libraries + AC_QUIET AC_CHECK_FUNCS $SRC $__aclhdrs && return 0 + + # then search through the list of libraries + __libs="$LIBS" + for x in $__acllibs; do + LIBS="$__libs $x" + if AC_QUIET AC_CHECK_FUNCS $SRC $__aclhdrs; then + AC_LIBS="$AC_LIBS $x" + return 0 + fi + done + return 1 +} + + +# +# AC_PROG_LEX checks to see if LEX exists, and if it's lex or flex. +# +AC_PROG_LEX() { + TLOGN "looking for lex " + + DEST=`acLookFor lex` + if [ "$DEST" ]; then + AC_MAK LEX + AC_DEFINE PATH_LEX \"$DEST\" + AC_SUB 'LEX' "$DEST" + echo "lex is $DEST" + else + DEST=`acLookFor flex` + if [ "$DEST" ]; then + AC_MAK FLEX + AC_DEFINE 'LEX' \"$DEST\" + AC_SUB 'LEX', "$DEST" + echo "lex is $DEST" + else + AC_SUB LEX '' + echo "neither lex or flex found" + TLOG " (not found)" + return 1 + fi + fi + + if AC_LIBRARY yywrap -ll -lfl; then + TLOG "($DEST)" + return 0 + fi + TLOG "(no lex library found)" + return 1 +} + + +# +# AC_PROG_YACC checks to see if YACC exists, and if it's bison or +# not. +# +AC_PROG_YACC () { + + TLOGN "looking for yacc " + + DEST=`acLookFor yacc` + if [ "$DEST" ]; then + AC_MAK YACC + AC_DEFINE PATH_YACC \"$DEST\" + AC_SUB 'YACC' "$DEST" + TLOG "($DEST)" + echo "yacc is $DEST" + else + DEST=`acLookFor bison` + if [ "$DEST" ]; then + AC_MAK BISON + AC_DEFINE 'YACC' \"$DEST\" + AC_SUB 'YACC' "$DEST -y" + echo "yacc is $DEST -y" + TLOG "($DEST -y)" + else + AC_SUB 'YACC' '' + echo "neither yacc or bison found" + TLOG " (not found)" + return 1 + fi + fi + return 0 +} + + +# +# AC_PROG_LN_S checks to see if ln exists, and, if so, if ln -s works +# +AC_PROG_LN_S () { + test "$AC_FIND_PROG" || AC_PROG_FIND + + test "$AC_FIND_PROG" || return 1 + + TLOGN "looking for \"ln -s\"" + DEST=`acLookFor ln` + + if [ "$DEST" ]; then + rm -f /tmp/b$$ + $DEST -s /tmp/a$$ /tmp/b$$ + if [ "`$AC_FIND_PROG /tmp/b$$ -type l -print`" ]; then + TLOG " ($DEST)" + echo "$DEST exists, and ln -s works" + AC_SUB 'LN_S' "$DEST -s" + rm -f /tmp/b$$ + else + AC_SUB 'LN_S' '' + TLOG " ($DEST exists, but -s does not seem to work)" + echo "$DEST exists, but ln -s doesn't seem to work" + rm -f /tmp/b$$ + return 1 + fi + else + AC_SUB 'LN_S' '' + echo "ln not found" + TLOG " (not found)" + return 1 + fi +} + + +# +# AC_PROG_FIND looks for the find program and sets the FIND environment +# variable +# +AC_PROG_FIND () { + if test -z "$AC_FIND_PROG"; then + MF_PATH_INCLUDE FIND find + rc=$? + AC_FIND_PROG=$DEST + return $rc + fi + return 0 +} + + +# +# AC_PROG_AWK looks for the awk program and sets the AWK environment +# variable +# +AC_PROG_AWK () { + if test -z "$AC_AWK_PROG"; then + MF_PATH_INCLUDE AWK awk + rc=$? + AC_AWK_PROG=$DEST + return $rc + fi + return 0 +} + + +# +# AC_PROG_SED looks for the sed program and sets the SED environment +# variable +# +AC_PROG_SED () { + if test -z "$AC_SED_PROG"; then + MF_PATH_INCLUDE SED sed + rc=$? + AC_SED_PROG=$DEST + return $rc + fi + return 0 +} + + +# +# AC_HEADER_SYS_WAIT looks for sys/wait.h +# +AC_HEADER_SYS_WAIT () { + AC_CHECK_HEADERS sys/wait.h || return 1 +} + +# +# AC_TYPE_PID_T checks to see if the pid_t type exists +# +AC_TYPE_PID_T () { + + AC_CHECK_TYPE pid_t sys/types.h + return $? +} + + +# +# AC_C_CONST checks to see if the compiler supports the const keyword +# +AC_C_CONST () { + cat > /tmp/pd$$.c << EOF +const char me=1; +EOF + LOGN "checking for \"const\" keyword" + + if __MAKEDOTO /tmp/pd$$.c; then + rc=0 + else + AC_DEFINE 'const' '/**/' + rc=1 + fi + rm -f /tmp/pd$$.c + return $rc +} + + +# +# AC_C_VOLATILE checks to see if the compiler supports the volatile keyword +# +AC_C_VOLATILE () { + cat > /tmp/pd$$.c << EOF +f() { volatile char me=1; } +EOF + LOGN "checking for \"volatile\" keyword" + + if __MAKEDOTO /tmp/pd$$.c; then + rc=0 + else + AC_DEFINE 'volatile' '/**/' + rc=1 + fi + rm -f /tmp/pd$$.c + return $rc +} + + +# +# AC_SCALAR_TYPES checks to see if the compiler can generate 2 and 4 byte ints. +# +AC_SCALAR_TYPES () { + cat > /tmp/pd$$.c << EOF +#include +main() +{ + unsigned long v_long; + unsigned int v_int; + unsigned short v_short; + + if (sizeof v_long == 4) + puts("#define DWORD unsigned long"); + else if (sizeof v_int == 4) + puts("#define DWORD unsigned int"); + else + exit(1); + + if (sizeof v_int == 2) + puts("#define WORD unsigned int"); + else if (sizeof v_short == 2) + puts("#define WORD unsigned short"); + else + exit(2); + puts("#define BYTE unsigned char"); + exit(0); +} +EOF + rc=1 + LOGN "defining WORD & DWORD scalar types" + if $AC_CC /tmp/pd$$.c -o /tmp/pd$$; then + if /tmp/pd$$ >> $__cwd/config.h; then + rc=0 + fi + fi + case "$rc" in + 0) TLOG "" ;; + *) TLOG " ** FAILED **" ;; + esac + rm -f /tmp/pd$$ /tmp/pd$$.c +} + + +# +# AC_OUTPUT generates makefiles from makefile.in's +# +AC_OUTPUT () { + cd $__cwd + AC_SUB 'LIBS' "$AC_LIBS" + AC_SUB 'CONFIGURE_FILES' "$__config_files" + AC_SUB 'GENERATED_FILES' "$*" + AC_SUB 'CFLAGS' "$AC_CFLAGS" + AC_SUB 'LDFLAGS' "$AC_LDFLAGS" + AC_SUB 'srcdir' "$AC_SRCDIR" + AC_SUB 'prefix' "$AC_PREFIX" + AC_SUB 'exedir' "$AC_EXECDIR" + AC_SUB 'sbindir' "$AC_SBINDIR" + AC_SUB 'libdir' "$AC_LIBDIR" + AC_SUB 'libexec' "$AC_LIBEXEC" + AC_SUB 'confdir' "$AC_CONFDIR" + AC_SUB 'mandir' "$AC_MANDIR" + + if [ -r config.sub ]; then + test "$AC_SED_PROG" || AC_PROG_SED + test "$AC_SED_PROG" || return 1 + + echo >> config.h + echo "#endif/* ${AC_CONFIGURE_FOR} */" >> config.h + + rm -f config.cmd + Q=\' + cat - > config.cmd << EOF +#! /bin/sh +${CC:+CC=${Q}${CC}${Q}} ${CFLAGS:+CFLAGS=${Q}${CFLAGS}${Q}} $ac_progname $ac_configure_command +EOF + chmod +x config.cmd + + __d=$AC_SRCDIR + for makefile in $*;do + if test -r $__d/${makefile}.in; then + LOG "generating $makefile" + ./config.md `__ac_dirname ./$makefile` 2>/dev/null + $AC_SED_PROG -f config.sub < $__d/${makefile}.in > $makefile + __config_files="$__config_files $makefile" + else + LOG "WARNING: ${makefile}.in does not exist!" + fi + done + unset __d + + else + echo + fi +} + +# +# AC_CHECK_FLOCK checks to see if flock() exists and if the LOCK_NB argument +# works properly. +# +AC_CHECK_FLOCK() { + + AC_CHECK_HEADERS sys/types.h sys/file.h fcntl.h + + cat << EOF > $$.c +#include +#include +#include +#include + +main() +{ + int x = open("$$.c", O_RDWR, 0666); + int y = open("$$.c", O_RDWR, 0666); + + if (flock(x, LOCK_EX) != 0) + exit(1); + if (flock(y, LOCK_EX|LOCK_NB) == 0) + exit(1); + exit(0); +} +EOF + + LOGN "checking for flock()" + HAS_FLOCK=0 + if $AC_CC -o flock $$.c ; then + if ./flock ; then + LOG " (found)" + HAS_FLOCK=1 + AC_DEFINE HAS_FLOCK + else + LOG " (bad)" + fi + else + LOG " (no)" + fi + + rm -f flock $$.c + + case "$HAS_FLOCK" in + 0) return 1 ;; + *) return 0 ;; + esac +} + + +# +# AC_CHECK_RESOLVER finds out whether the berkeley resolver is +# present on this system. +# +AC_CHECK_RESOLVER () { + AC_PROG_CC + + TLOGN "checking for the Berkeley resolver library" + + __ACR_rc=0 + + cat > /tmp/ngc$$.c << EOF +#include +#include +#include +#include + +main() +{ + char bfr[256]; + + res_init(); + res_query("hello", C_IN, T_A, bfr, sizeof bfr); +} +EOF + + if $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c; then + TLOG " (found)" + elif $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c -lresolv; then + TLOG " (found, needs -lresolv)" + AC_LIBS="$AC_LIBS -lresolv" + elif $AC_CC -DBIND_8_COMPAT -o /tmp/ngc$$ /tmp/ngc$$.c; then + TLOG " (found, needs BIND_8_COMPAT)" + AC_DEFINE BIND_8_COMPAT 1 + elif $AC_CC -DBIND_8_COMPAT -o /tmp/ngc$$ /tmp/ngc$$.c -lresolv; then + TLOG " (found, needs BIND_8_COMPAT & -lresolv)" + AC_DEFINE BIND_8_COMPAT 1 + else + TLOG " (not found)" + __ACR_rc=1 + fi + rm -f /tmp/ngc$$.c + return $__ACR_rc +} + + +# +# AC_CHECK_ALLOCA looks for alloca +# +AC_CHECK_ALLOCA () { + + AC_PROG_CC + AC_CHECK_HEADERS stdlib.h + + cat - > /tmp/ngc$$.c << EOF +#if T +# include +#else +# include +#endif +main() +{ + alloca(10); +} +EOF + + LOGN "checking for the alloca function" + if $AC_CC -DT /tmp/ngc$$.c -o /tmp/ngc$$; then + AC_DEFINE 'HAVE_ALLOCA_H' 1 + status=0 + TLOG " (found in alloca.h)" + elif $AC_CC /tmp/ngc$$.c -o /tmp/ngc$$; then + TLOG " (found)" + status=0 + else + TLOG " (not found)" + status=1 + fi + rm -f /tmp/ngc$$.c /tmp/ngc + return $status + +} + + +# +# AC_PROG_INSTALL finds the install program and guesses whether it's a +# Berkeley or GNU install program +# +AC_PROG_INSTALL () { + + DEST=`acLookFor install` + + LOGN "checking for install" + unset IS_BSD + if [ "$DEST" ]; then + # BSD install or GNU install? Let's find out... + touch /tmp/a$$ + + $DEST /tmp/a$$ /tmp/b$$ + + if test -r /tmp/a$$; then + LOG " ($DEST)" + else + IS_BSD=1 + LOG " ($DEST) bsd install" + fi + rm -f /tmp/a$$ /tmp/b$$ + else + DEST=`acLookFor ginstall` + if [ "$DEST" ]; then + LOG " ($DEST)" + else + DEST="false" + LOG " (not found)" + fi + fi + + if [ "$IS_BSD" ]; then + PROG_INSTALL="$DEST -c" + else + PROG_INSTALL="$DEST" + fi + + AC_SUB 'INSTALL' "$PROG_INSTALL" + AC_SUB 'INSTALL_PROGRAM' "$PROG_INSTALL -s -m 755" + AC_SUB 'INSTALL_DATA' "$PROG_INSTALL -m 444" + + # finally build a little directory installer + # if mkdir -p works, use that, otherwise use install -d, + # otherwise build a script to do it by hand. + # in every case, test to see if the directory exists before + # making it. + + if mkdir -p $$a/b; then + # I like this method best. + __mkdir="mkdir -p" + rmdir $$a/b + rmdir $$a + elif $PROG_INSTALL -d $$a/b; then + __mkdir="$PROG_INSTALL -d" + rmdir $$a/b + rmdir $$a + fi + + __config_files="$__config_files config.md" + AC_SUB 'INSTALL_DIR' "$__cwd/config.md" + echo "#! /bin/sh" > $__cwd/config.md + echo "# script generated" `date` "by configure.sh" >> $__cwd/config.md + echo >> $__cwd/config.md + if [ "$__mkdir" ]; then + echo "test -d \"\$1\" || $__mkdir \"\$1\"" >> $__cwd/config.md + echo "exit $?" >> $__cwd/config.md + else + cat - >> $__cwd/config.md << \EOD +pieces=`IFS=/; for x in $1; do echo $x; done` +dir= +for x in $pieces; do + dir="$dir$x" + mkdir $dir || exit 1 + dir="$dir/" +done +exit 0 +EOD + fi + chmod +x $__cwd/config.md +} + +# +# acCheckCPP is a local that runs a C preprocessor with a given set of +# compiler options +# +acCheckCPP () { + cat > /tmp/ngc$$.c << EOF +#define FOO BAR + +FOO +EOF + + if $1 $2 /tmp/ngc$$.c > /tmp/ngc$$.o; then + if grep -v '#define' /tmp/ngc$$.o | grep -s BAR >/dev/null; then + echo "CPP=[$1], CPPFLAGS=[$2]" + AC_SUB 'CPP' "$1" + AC_SUB 'CPPFLAGS' "$2" + rm /tmp/ngc$$.c /tmp/ngc$$.o + return 0 + fi + fi + rm /tmp/ngc$$.c /tmp/ngc$$.o + return 1 +} + + +# +# AC_PROG_CPP checks for cpp, then checks to see which CPPFLAGS are needed +# to run it as a filter. +# +AC_PROG_CPP () { + if [ "$AC_CPP_PROG" ]; then + DEST=$AC_CPP_PROG + else + __ac_path="$AC_PATH" + AC_PATH="/lib:/usr/lib:${__ac_path:-$ac_default_path}" + DEST=`acLookFor cpp` + AC_PATH="$__ac_path" + fi + + unset fail + LOGN "Looking for cpp" + if [ "$DEST" ]; then + TLOGN " ($DEST)" + acCheckCPP $DEST "$CPPFLAGS" || \ + acCheckCPP $DEST -traditional-cpp -E || \ + acCheckCPP $DEST -E || \ + acCheckCPP $DEST -traditional-cpp -pipe || \ + acCheckCPP $DEST -pipe || fail=1 + + if [ "$fail" ]; then + AC_FAIL " (can't run cpp as a pipeline)" + else + TLOG " ok" + return 0 + fi + fi + AC_FAIL " (not found)" +} + +# +# AC_FAIL spits out an error message, then __fail's +AC_FAIL() { + LOG "$*" + $__fail 1 +} + +# +# AC_SUB writes a substitution into config.sub +AC_SUB() { + ( echononl "s;@$1@;" + _subst=`echo $2 | sed -e 's/;/\\;/g'` + echononl "$_subst" + echo ';g' ) >> $__cwd/config.sub +} + +# +# AC_MAK writes a define into config.mak +AC_MAK() { + echo "HAVE_$1 = 1" >> $__cwd/config.mak +} + +# +# AC_DEFINE adds a #define to config.h +AC_DEFINE() { + echo "#define $1 ${2:-1}" >> $__cwd/config.h +} + +# +# AC_INCLUDE adds a #include to config.h +AC_INCLUDE() { + echo "#include \"$1\"" >> $__cwd/config.h +} + +# +# AC_CONFIG adds a configuration setting to all the config files +AC_CONFIG() { + AC_DEFINE "PATH_$1" \""$2"\" + AC_MAK "$1" + AC_SUB "$1" "$2" +} + +# +# AC_QUIET does something quietly +AC_QUIET() { + eval $* 5>/dev/null +} diff --git a/configure.sh b/configure.sh new file mode 100755 index 0000000..bb6073b --- /dev/null +++ b/configure.sh @@ -0,0 +1,29 @@ +#! /bin/sh + +# local options: ac_help is the help message that describes them +# and LOCAL_AC_OPTIONS is the script that interprets them. LOCAL_AC_OPTIONS +# is a script that's processed with eval, so you need to be very careful to +# make certain that what you quote is what you want to quote. + +# load in the configuration file +# +TARGET=markdown +. ./configure.inc + +AC_INIT $TARGET + +AC_PROG_CC + +case "$AC_CC $AC_CFLAGS" in +*-Wall*) AC_DEFINE 'while(x)' 'while( (x) != 0 )' + AC_DEFINE 'if(x)' 'if( (x) != 0 )' ;; +esac + +AC_C_VOLATILE +AC_C_CONST +AC_SCALAR_TYPES +AC_CHECK_ALLOCA || AC_FAIL "$TARGET requires alloca()" + +[ "$OS_FREEBSD" -o "$OS_DRAGONFLY" ] || AC_CHECK_HEADERS malloc.h + +AC_OUTPUT Makefile diff --git a/cstring.h b/cstring.h new file mode 100644 index 0000000..4d361dc --- /dev/null +++ b/cstring.h @@ -0,0 +1,49 @@ +/* two template types: STRING(t) which defines a pascal-style string + * of element (t) [STRING(char) is the closest to the pascal string], + * and ANCHOR(t) which defines a baseplate that a linked list can be + * built up from. [The linked list /must/ contain a ->next pointer + * for linking the list together with.] + */ +#ifndef _CSTRING_D +#define _CSTRING_D + +#include + +/* expandable Pascal-style string. + */ +#define STRING(type) struct { type *text; int size, alloc; } + +#define RESERVE(x,c) (x).text = malloc(sizeof T(x)[0] * (((x).size=0),((x).alloc=(c))) ) +#define CREATE(x) RESERVE(x,100) +#define EXPAND(x) (x).text[((x).size < (x).alloc \ + ? 0 \ + : !((x).text = realloc((x).text, sizeof T(x)[0] * ((x).alloc += 100)))), \ + (x).size++] + +#define DELETE(x) (x).alloc ? (free(T(x)), S(x) x.alloc = 0) \ + : ( S(x) = 0 ) +#define CLIP(t,i,sz) \ + ( ((i) >= 0) && ((sz) > 0) && (((i)+(sz)) <= S(t)) ) ? \ + (memmove(&T(t)[i], &T(t)[i+sz], (S(t)-(i+sz)+1)*sizeof(T(t)[0])), \ + S(t) -= (sz)) : -1 + +/* reference-style links (and images) are stored in an array + */ +#define T(x) (x).text +#define S(x) (x).size + +/* abstract anchor type that defines a list base + * with a function that attaches an element to + * the end of the list. + * + * the list base field is named .text so that the T() + * macro will work with it. + */ +#define ANCHOR(t) struct { t *text, *end; } + +#define ATTACH(t, p) ( (t).text ?( ((t).end->next = (p)), ((t).end = (p)) ) \ + :( ((t).text = (t).end = (p)) ) ) + +typedef STRING(char) Cstring; + +#endif/*_CSTRING_D*/ diff --git a/markdown.c b/markdown.c new file mode 100644 index 0000000..baab81b --- /dev/null +++ b/markdown.c @@ -0,0 +1,1314 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + */ +#include +#include +#include +#include +#include +#include "cstring.h" + +/* prefixes for + */ +static char *autoprefix[] = { "http://", "https://", "ftp://", "news://" }; +#define SZAUTOPREFIX (sizeof autoprefix / sizeof autoprefix[0]) + +/* block-level tags for passing html blocks through the blender + */ +static char *blocktags[] = { "ADDRESS", "BDO", "BLOCKQUOTE", "CENTER", + "DFN", "DIV", "H1", "H2", "H3", "H4", + "H5", "H6", "LISTING", "NOBR", "UL", + "P", "OL", "DL", "PLAINTEXT", "PRE", + "WBR", "XMP" }; +#define SZBLOCKTAGS (sizeof blocktags / sizeof blocktags[0]) + +/* reference-style links (and images) are stored in an array + * of footnotes. + */ +typedef struct footnote { + Cstring tag; /* the tag for the reference link */ + Cstring link; /* what this footnote points to */ + Cstring title; /* what it's called (TITLE= attribute) */ + int height, width; /* dimensions (for image link) */ +} Footnote; + +static STRING(Footnote) footnotes; + + +/* each input line is read into a Line, which contains the line, + * the offset of the first non-space character [this assumes + * that all tabs will be expanded to spaces!], and a pointer to + * the next line. + */ +typedef struct line { + Cstring text; + struct line *next; + int dle; +} Line; + + +/* a paragraph is a collection of Lines, with links to the next paragraph + * and (if it's a QUOTE, UL, or OL) to the reparsed contents of this + * paragraph. + */ +typedef struct paragraph { + struct paragraph *next; /* next paragraph */ + struct paragraph *down; /* recompiled contents of this paragraph */ + struct line *text; /* all the text in this paragraph */ + enum { FORCED, CODE=1, QUOTE, MARKUP, HTML, UL, OL, HR } typ; + int para; + enum { LEFT, RIGHT, CENTER} align; +} Paragraph; + + +/* case insensitive string sort (for qsort() and bsearch() of block tags) + */ +static int +casort(char **a, char **b) +{ + return strcasecmp(*a,*b); +} + + +/* case insensitive string sort for Footnote tags. + */ +static int +footsort(Footnote *a, Footnote *b) +{ + return strcasecmp(T(a->tag), T(b->tag)); +} + + +/* see if a is one of the block tags that frames a html + * paragraph. + */ +static char * +isblocktag(char *tag) +{ + char **r=bsearch(&tag, blocktags, SZBLOCKTAGS, sizeof blocktags[0], casort); + + return r ? (*r) : 0; +} + + +/* find the first blank character after position + */ +static int +nextblank(Line *t, int i) +{ + while ( (i < S(t->text)) && !isspace(T(t->text)[i]) ) + ++i; + return i; +} + + +/* find the next nonblank character after position + */ +static int +nextnonblank(Line *t, int i) +{ + while ( (i < S(t->text)) && isspace(T(t->text)[i]) ) + ++i; + return i; +} + + +/* find the first nonblank character on the Line. + */ +static int +firstnonblank(Line *p) +{ + return nextnonblank(p,0); +} + + +/* read in the markdown source document, assemble into a linked + * list. Tabs ('\t') will be expanded to spaces here to make + * the rest of the compilation easier. + */ +Line * +in(FILE *input) +{ + int i, c, xp; + Line *p; + + ANCHOR(Line) list = { 0, 0 }; + + p = calloc(sizeof *p, 1); + CREATE(p->text); + xp = 0; + + for (; (c = getc(input)) != EOF; ) { + if (c == '\n') { + /* add a trailing null, then exclude it from + * the string size + */ + EXPAND(p->text) = 0; + --S(p->text); + + p->dle = firstnonblank(p); + + ATTACH(list, p); + + p = calloc(sizeof *p, 1); + CREATE(p->text); + xp = 0; + } + else if ( c != '\t' ) { + EXPAND(p->text) = c; + xp++; + } + else { + /* expand tabs into 1..4 spaces. This is not + * the traditional tab spacing, but the language + * definition /really really/ wants tabs to be + * 4 spaces wide (indents are in terms of tabs + * *or* 4 spaces. + */ + do { + EXPAND(p->text) = ' '; + } while ( ++xp & 03 ); + } + } + if ( S(p->text) ) { + /* It's not an error if the input doesn't end on a newline + */ + EXPAND(p->text) = 0; + --S(p->text); + + p->dle = firstnonblank(p); + + ATTACH(list, p); + } + return T(list); +} + + +static Cstring output; +static unsigned int csp = 0; + +static int +push(char *t, int s) +{ + int i; + + for (i=0; i < s; i++) + EXPAND(output) = t[i]; +} + + +/* look characters ahead of the cursor. + */ +static int +peek(int i) +{ + + if ( i > 0 ) + i += (csp-1); + else + i = (csp + i)-1; + + return (i >= 0) && (i < S(output)) ? T(output)[i] : EOF; +} + + +static int +pull() +{ + if ( csp < S(output) ) return T(output)[csp++]; + + S(output) = csp = 0; + return EOF; +} + + +static void +skipblankc() +{ + while ( isspace(peek(1)) ) + pull(); +} + + +static void +pullcopy(char *dest, int size) +{ + while ( size-- > 0 ) + *dest++ = pull(); + *dest = 0; +} + +static void +linkylinky(int image) +{ + char *label; + int labelsize, size; + int qc, c, i, j; + + for ( labelsize = 1; (c=peek(labelsize)) != ']'; ++labelsize) + if ( c == EOF ) + return; + + label = alloca(labelsize+1); + pullcopy(label, labelsize); + + skipblankc(); + + printf( image ? "link) ) + printf(" %s=\"%.*s\"", image ? "src" : "href", + S(ret->link), T(ret->link)); + if ( S(ret->title) ) + printf(" title=\"%.*s\"", S(ret->title), T(ret->title)); + + if ( image && ret->height && ret->width ) + printf(" height=%d width=%d", ret->height, ret->width); + } + } + else + return; + + printf(image ? " alt=\"%.*s\"" : ">%.*s'); +} + + +static char* +hexfmt() +{ + return (random()&1) ? "&#x%02x;" : "&#%02d;"; +} + + +static int +handle_less_than() +{ + char *text; + int c, size, i; + int maybetag=1, maybelink=0, maybeaddress=0; + + for ( size=0; ((c = peek(size+1)) != '>') && !isspace(c); size++ ) { + if ( ! (c == '/' || isalnum(c)) ) + maybetag=0; + if ( c == '@' ) + maybeaddress=1; + else if ( c == EOF ) + return 0; + } + + if ( size == 0 ) + return 0; + + if ( maybetag ) { + putchar('<'); + return 1; + } + + text = alloca(size+2); + pullcopy(text,size); + + for ( i=0; i < SZAUTOPREFIX; i++ ) + if ( strncasecmp(text, autoprefix[i], strlen(autoprefix[i])) == 0 ) { + printf("%s"); + for ( i=0; text[i]; i++ ) + printf(hexfmt(), (unsigned char)text[i]); + printf(""); + return 1; + } + + printf("<%s", text); + return 1; +} + +static void code(int); + +static void +text() +{ + int c, j; + int em = 0; + int strong = 0; + int dquo = 0; + int squo = 0; + + while ( (c = pull()) != EOF ) { + switch (c) { + case 0: break; + +/* Markdown transformations (additional chrome is done at the end of this + * switch) + */ + case '!': if ( peek(1) == '[' ) { + pull(); + linkylinky(1); + } + else + putchar(c); + break; + case '[': linkylinky(0); + break; + case '*': + case '_': if (peek(1) == c) { + pull(); + if ( c == strong ) { + printf(""); + strong = 0; + } + else if ( strong == 0 ) { + printf(""); + strong = c; + } + else { + putchar(c); + putchar(c); + } + } + else { + if (c == em ) { + printf(""); + em = 0; + } + else if ( em == 0 ) { + printf(""); + em = c; + } + else + putchar(c); + } + break; + + case '`': printf(""); + if ( peek(1) == '`' ) { + pull(); + code(2); + } + else + code(1); + break; + + case '\\': if ( (c = pull()) == '&' ) + printf("&"); + else if ( c == '<' ) + printf("<"); + else + putchar( c ? c : '\\'); + break; + + case '<': if ( !handle_less_than() ) + printf("<"); + break; + + case '&': j = (peek(1) == '#' ) ? 2 : 1; + while ( isalnum(peek(j)) ) + ++j; + + if ( peek(j) != ';' ) + printf("&"); + else + putchar(c); + break; + + default: putchar(c); + break; + +/* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm) + */ + case '"': printf("&%cdquo;", dquo ? 'r' : 'l' ); + dquo = !dquo; + break; + +#if 0 + case '\'': printf("&%csquo;", squo ? 'r' : 'l' ); + squo = !squo; + break; +#endif + + case '.': if ( peek(1) == '.' && peek(2) == '.' ) { + printf("…"); + pull();pull(); + } + else + putchar(c); + break; + + case '-': if ( peek(1) == '-' ) { + printf("—"); + pull(); + } + else if ( isspace(peek(-1)) && isspace(peek(1)) ) + printf("–"); + else + putchar(c); + break; + + case '(': if ( (j = toupper(peek(1))) == 'R' || j == 'C' ) { + if ( peek(2) == ')' ) { + printf( "&%s;", (j=='C') ? "copy" : "reg" ); + pull();pull(); + break; + } + } + else if ( j == 'T' && toupper(peek(2)) == 'M' + && peek(3) == ')' ) { + printf("™"); + pull();pull();pull(); + break; + } + putchar(c); + break; + } + } +} + + +static void +code(int escape) +{ + int c, j; + + while ( (c = pull()) != EOF ) { + switch (c) { + case '`': switch (escape) { + case 2: if ( peek(1) == '`' ) { + pull(); + case 1: printf(""); + return; + } + } + putchar(c); + break; + + case '&': printf("&"); break; + case '<': printf("<"); break; + default: putchar(c); break; + } + } + +} + + + +static int +alto(char *t, int s) +{ + int i; + + for (i=0; (i < s) && isalnum(t[i]); i++) + ; + + return (i < s) ? i : 0; + + +} + +/* setext header; 2 lines, second is ==== or ----- + */ +static int +setext(Line *p) +{ + char *q; + int i; + + if ( (p->next == 0) || p->next->next ) return 0; + + q = T(p->next->text); + if ( (*q == '=') || (*q == '-') ) { + for (i=1; i < S(p->next->text); i++) + if ( *q != T(p->next->text)[i] ) + return 0; + + i = (*q == '=') ? 1 : 2; + + printf("", i); + push(T(p->text), S(p->text)); text(); + printf("\n", i); + + return 1; + } + return 0; +} + + +/* etx header; # of #'s in front is the H level + */ +static int +etx(Line *p) +{ + int H = 0; + int i, j; + + if ( p->next ) return 0; + + for ( i=0; T(p->text)[i] == '#'; ++i, ++H) + ; + + if ( !H ) return 0; + + i = nextnonblank(p, i); + + j = S(p->text)-1; + + while ( (j > i) && (T(p->text)[j] == '#') ) + --j; + + while ( (j > 1) && isspace(T(p->text)[j]) ) + --j; + + if ( j < i ) return 0; + + printf("", H); + push(T(p->text)+i, 1+(j-i)); text(); + printf("", H); + return 1; +} + + +void +printblock(Line *t) +{ + while (t) { + if ( S(t->text) ) { + if ( S(t->text) > 2 && T(t->text)[S(t->text)-2] == ' ' + && T(t->text)[S(t->text)-1] == ' ') { + push(T(t->text), S(t->text)-2); + push("
\n", 6); + } + else { + push(T(t->text), S(t->text)); + push("\n", 1); + } + } + t = t->next; + } + text(); +} + + +void +printcode(Line *t) +{ + int blanks; + + for ( blanks = 0; t ; t = t->next ) + if ( S(t->text) ) { + while ( blanks ) { + push("\n", 1); + --blanks; + } + push(T(t->text), S(t->text)); + push("\n", 1); + } + else blanks++; + code(0); +} + + +void +printhtml(Line *t) +{ + int blanks; + + for ( blanks=0; t ; t = t->next ) + if ( S(t->text) ) { + while ( blanks ) { + putchar('\n'); + --blanks; + } + fwrite(T(t->text), S(t->text), 1, stdout); + putchar('\n'); + } + else + blanks++; +} + + +static Paragraph *display(Paragraph*, int); + + +static void +emit(Paragraph *p) +{ + int multiple = p->next; + + while ( p = display(p, multiple) ) + ; +} + + +static Paragraph* +listdisplay(Paragraph *p) +{ + int typ = p->typ; + + printf( "<%cl>\n", (typ==UL)?'u':'o'); + + while ( p && (p->typ == typ) ) { + printf("
  • "); + emit(p->down); + printf("
  • "); + + p = p->next; + } + + printf( "\n", (typ==UL)?'u':'o'); + return p; +} + + +static int +nextislist(Paragraph *p) +{ + return p && (p->typ == UL || p->typ == OL); +} + + +/* dump out a Paragraph in the desired manner + */ +static Paragraph* +display(Paragraph *p, int multiple) +{ + char *pp = 0; + + switch ( p->typ ) { + case FORCED: + break; + + case HTML: + printhtml(p->text); + break; + + case CODE: + printf("

    ");
    +	printcode(p->text);
    +	puts("

    "); + break; + + case QUOTE: + printf("
    "); + emit(p->down); + puts("
    "); + break; + + case UL: + case OL: + return listdisplay(p); + + case HR: + puts("
    "); + break; + + default: + if ( p->align == CENTER ) + pp = "center"; + else if ( multiple && p->para ) + pp = "p"; + + if ( pp ) printf("<%s>", pp); + if ( !(setext(p->text)||etx(p->text)) ) + printblock(p->text); + if ( pp ) printf("\n", pp); + break; + } + return p->next; +} + + +static int +blankline(Line *p) +{ + return ! (p && (S(p->text) > p->dle) ); +} + + +static char * +isopentag(Line *p) +{ + int i=0, len; + char *key; + + if ( !p ) return 0; + + len = S(p->text); + + if ( len < 3 || T(p->text)[0] != '<' ) + return 0; + + for (i=1; (i < len) && isalnum(T(p->text)[i]); ++i) + /* skip over tag (don't care about arguments or '>' yet) */ ; + + key = alloca(i); + memcpy(key, T(p->text)+1, i-1); + key[i-1] = 0; + + return isblocktag(key); +} + + +static int +isclosetag(Line *p, char *key, int siz) +{ + int eol; + + if ( !p ) return 0; + + eol = S(p->text)-1; + + if ( T(p->text)[eol] != '>' ) return 0; + if ( strncasecmp(T(p->text)+(eol-siz), key, siz) != 0 ) return 0; + if ( T(p->text)[eol-(siz+1)] != '/' ) return 0; + if ( T(p->text)[eol-(siz+2)] != '<' ) return 0; + return 1; +} + + +static Line * +htmlblock(Paragraph *p, char *key) +{ + Line *t, *ret; + int keysize = strlen(key); + + for ( t = p->text; t ; t = t->next) + if ( isclosetag(t, key, keysize) && blankline(t->next) ) { + ret = t->next; + t->next = 0; + return ret; + } + return t; +} + + +/* footnotes look like ^{0,3}[stuff]: $ + */ +static int +isfootnote(Line *t) +{ + int i; + + if ( ( (i = t->dle) > 3) || (T(t->text)[i] != '[') ) + return 0; + + for ( ; i < S(t->text) ; ++i ) { + if ( T(t->text)[i] == ']' && T(t->text)[i+1] == ':' ) + return 1; + } + return 0; +} + + +static int +islabel(char *line) +{ + return strchr("*-+", line[0]) && isspace(line[1]); +} + +static int +isquote(Line *t) +{ + return ( T(t->text)[0] == '>' ); +} + + +static int +islist(Line *t, int *trim) +{ + int i, j; + char *q; + + if ( (t == 0) || (t->dle > 3) ) + return 0; + + if ( islabel(T(t->text) + t->dle) ) { + i = nextnonblank(t, t->dle+1); + *trim = i; + return UL; + } + + if ( (j = nextblank(t,t->dle)) > t->dle ) { + if ( T(t->text)[j-1] == '.' ) { + strtoul(T(t->text)+t->dle, &q, 10); + if ( q == T(t->text) + (j-1) ) { + j = nextnonblank(t,j); + *trim = j; + return OL; + } + } + } + return 0; +} + + +static int +dashchar(char c) +{ + return (c == '*') || (c == '-') || (c == '_'); +} + + +static int +ishr(Line *t) +{ + int i, count; + char dash = 0; + char c; + + for ( i = 0, count = 1; i < S(t->text); i++) { + c = T(t->text)[i]; + if ( dashchar(c) ) { + if ( dash == c ) + ++count; + else if ( dash == 0 ) + dash = c; + else + return 0; + } + else if ( !isspace(c) ) + return 0; + } + return (count >= 3); +} + + +static int +iscode(Line *t) +{ + return (t->dle >= 4); +} + + +static int +fancy(Line *t) +{ + int trim; + + return isquote(t) || iscode(t) || islist(t, &trim); +} + + +static Line * +codeblock(Paragraph *p) +{ + Line *t, *r, *ret; + + for ( t = p->text; t; t = r ) { + CLIP(t->text,0,4); + t->dle = firstnonblank(t); + + r = t->next; + if ( r && S(r->text) && !iscode(r) ) { + ret = t->next; + t->next = 0; + return ret; + } + } + return t; +} + + +static int +centered(Line *first, Line *last) +{ + if ( first&&last ) { + int len = S(last->text); + + if ( (len > 2) && (strncmp(T(first->text), "->", 2) == 0) + && (strncmp(T(last->text)+len-2, "<-", 2) == 0) ) { + CLIP(first->text, 0, 2); + S(last->text) -= 2; + return CENTER; + } + } + return LEFT; +} + + +static Line * +textblock(Paragraph *p) +{ + Line *t, *r; + + for ( t = p->text; t ; t = t->next ) { + if ( (r = t->next) == 0 ) { + p->para = 2; + p->align = centered(p->text, t); + return 0; + } + else if ( fancy(r) ){ + t->next = 0; + return r; + } + else if ( blankline(r) ) { + p->align = centered(p->text, t); + p->para = 2; + t->next = 0; + return r; + } + } + return t; +} + + +static Line * +skipempty(Line *p) +{ + while ( p && (p->dle == S(p->text)) ) + p = p->next; + return p; +} + + +static int +quoteprefix(Line *t) +{ + if ( T(t->text)[0] != '>' ) + return 0; + + return ( T(t->text)[1] == ' ' ) ? 2 : 1; +} + + +static Line * +quoteblock(Paragraph *p) +{ + Line *t, *blank, *last = 0; + int qp; + + for ( t = p->text; t ; t = t->next ) { + if ( last && blankline(t) ) { + blank = last; + t = skipempty(t); + + if ( !isquote(t) ) { + blank->next = 0; + return t; + } + last = blank; + } + else + last = t; + + if ( (qp = quoteprefix(t)) > 0 ) { + CLIP(t->text, 0, qp); + t->dle = firstnonblank(t); + } + } + return t; +} + + +static Line * +listblock(Paragraph *p, int trim) +{ + Line *t = p->text; + Line *first = t, *blank, *last = 0; + + do { + if ( last ) { + if ( islist(t, &trim) ) { + last->next = 0; + return t; + } + else if ( blankline(t) ) { + last = t; + t = skipempty(t); + + if ( (t == 0) || (t->dle < 4) ) { + last->next = 0; + return t; + } + } + else { + last = t; + t = t->next; + } + } + else { + last = t; + t = t->next; + } + CLIP(last->text,0,trim); + last->dle = firstnonblank(last); + + if ( t ) trim = (t->dle < 4) ? t->dle : 4; + } while ( t ); + return t; +} + + +static int +tgood(char c) +{ + switch (c) { + case '\'': + case '"': return c; + case '(': return ')'; + } + return 0; +} + + +/* + * add a new (image or link) footnote to the footnote table + */ +static Line* +addfootnote(Line *p) +{ + int j, i; + int c; + Line *p2 = p->next; + + Footnote *foot = &EXPAND(footnotes); + + CREATE(foot->tag); + CREATE(foot->link); + CREATE(foot->title); + foot->height = foot->width = 0; + + for (j=i=p->dle; T(p->text)[j] != ']'; j++) + EXPAND(foot->tag) = T(p->text)[j]; + + EXPAND(foot->tag) = T(p->text)[j++]; + j = nextnonblank(p, j+1); + + while ( (j < S(p->text)) && !isspace(T(p->text)[j]) ) + EXPAND(foot->link) = T(p->text)[j++]; + j = nextnonblank(p,j); + + if ( T(p->text)[j] == '=' ) { + sscanf(T(p->text)+j, "=%dx%d", &foot->width, &foot->height); + while ( (j < S(p->text)) && !isspace(T(p->text)[j]) ) + ++j; + j = nextnonblank(p,j); + } + + + if ( (j >= S(p->text)) && p2 && p2->dle && tgood(T(p2->text)[p2->dle]) ) { + j = p2->dle; + p = p2; + } + + if ( (c = tgood(T(p->text)[j])) ) + while ( (j < S(p->text)-1) && (T(p->text)[++j] != c) ) + EXPAND(foot->title) = T(p->text)[j]; + + return p->next; +} + + +typedef ANCHOR(Paragraph) Document; + + +/* + * allocate a paragraph header, link it to the + * tail of the current document + */ +static Paragraph * +Pp(Document *d, Line *ptr, int typ, int para) +{ + Paragraph *ret = calloc(sizeof *ret, 1); + + ret->text = ptr; + ret->typ = typ; + ret->para = para; + ret->align = LEFT; + + return ATTACH(*d, ret); +} + + +/* + * break a collection of markdown input into + * blocks of lists, code, html, and text to + * be marked up. + */ +static Paragraph * +compile(Line *ptr, int toplevel) +{ + Document d = { 0, 0 }; + Paragraph *p; + char *key; + int list_type, indent; + + while ( ptr = skipempty(ptr) ) { + if ( toplevel && (key = isopentag(ptr)) ) { + p = Pp(&d, ptr, HTML, 0); + ptr = htmlblock(p, key); + } + else if ( iscode(ptr) ) { + p = Pp(&d, ptr, CODE, 0); + ptr = codeblock(p); + } + else if ( ishr(ptr) ) { + p = Pp(&d, 0, HR, 0); + ptr = ptr->next; + } + else if ( list_type = islist(ptr, &indent) ) { + p = Pp(&d, ptr, list_type, 0); + ptr = listblock(p, indent); + + p->down = compile(p->text, 0); + } + else if ( isquote(ptr) ) { + p = Pp(&d, ptr, QUOTE, 0); + ptr = quoteblock(p); + p->down = compile(p->text, 0); + } + else if ( toplevel && (isfootnote(ptr)) ) { + ptr = addfootnote(ptr); + continue; + } + else { + p = Pp(&d, ptr, MARKUP, toplevel); + ptr = textblock(p); + } + } + return T(d); +} + + +static void +freeLine(Line *p) +{ + if (p->next) freeLine(p->next); + free(T(p->text)); + free(p); +} + + +static void +freeParagraph(Paragraph *p) +{ + Line *t; + + if (p->next) freeParagraph(p->next); + if (p->down) freeParagraph(p->down); + else if (p->text) freeLine(p->text); + free(p); +} + + +static void +freefootnotes() +{ + int i; + + for (i=0; i < S(footnotes); i++) { + free(T(T(footnotes)[i].tag)); + free(T(T(footnotes)[i].link)); + free(T(T(footnotes)[i].title)); + } + S(footnotes) = 0; +} + + +static void +initmarkdown() +{ + static int init = 0; + + if ( init ) return; + + srandom((unsigned int)time(0)); + qsort(blocktags, SZBLOCKTAGS, sizeof blocktags[0], casort); + CREATE(footnotes); + CREATE(output); + init = 1; +} + + +void +markdown(Line *text) +{ + Paragraph *paragraph; + initmarkdown(); + + paragraph = compile(text, 1); + qsort(T(footnotes), S(footnotes), sizeof T(footnotes)[0], footsort); + + emit(paragraph); + + freefootnotes(); + freeParagraph(paragraph); +} + + +#if PROGRAM +main(int argc, char **argv) +{ + + if ( (argc > 1) && !freopen(argv[1], "r", stdin) ) { + perror(argv[1]); + exit(1); + } + markdown(in(stdin)); + exit(0); +} +#endif