From 7de6920e445f774437608e8c9ae8f934f1b9d830 Mon Sep 17 00:00:00 2001 From: Zoviet Date: Sat, 13 Dec 2025 00:40:57 +0400 Subject: [PATCH] fork --- COPYRIGHT | 30 + CREDITS | 35 + Csio.c | 62 + INSTALL | 67 + Makefile.in | 208 +++ Plan9/README.md | 44 + Plan9/markdown.1 | 169 +++ Plan9/markdown.2 | 332 +++++ Plan9/markdown.6 | 541 ++++++++ Plan9/mkfile | 45 + README | 16 + README.md | 44 +- VERSION | 1 + amalloc.c | 142 +++ amalloc.h | 29 + basename.c | 42 + cmake/CMakeLists.txt | 266 ++++ cmake/config.h.in | 79 ++ cmake/discount-config.cmake.in | 1 + configure.inc | 2019 +++++++++++++++++++++++++++++ configure.sh | 296 +++++ css.c | 85 ++ cstring.h | 78 ++ docheader.c | 54 + dumptree.c | 163 +++ emmatch.c | 189 +++ flagprocs.c | 50 + flags.c | 137 ++ generate.c | 2164 ++++++++++++++++++++++++++++++++ gethopt.3 | 195 +++ gethopt.c | 347 +++++ gethopt.h | 44 + github_flavoured.c | 103 ++ h1title.c | 38 + html5.c | 16 + libmarkdown.pc.in | 11 + main.c | 378 ++++++ makepage.1 | 45 + makepage.c | 116 ++ markdown.1 | 201 +++ markdown.3 | 156 +++ markdown.7 | 1020 +++++++++++++++ markdown.c | 1546 +++++++++++++++++++++++ markdown.h | 323 +++++ mkd-callbacks.3 | 77 ++ mkd-extensions.7 | 197 +++ mkd-functions.3 | 186 +++ mkd-line.3 | 41 + mkd2html.1 | 52 + mkd2html.c | 235 ++++ mkdio.c | 527 ++++++++ mkdio.h.in | 148 +++ mktags.c | 93 ++ msvc/Makefile | 65 + msvc/README.md | 17 + msvc/config.h.vc | 77 ++ notspecial.c | 43 + pgm_options.c | 218 ++++ pgm_options.h | 8 + resource.c | 178 +++ setup.c | 31 + tags.c | 123 ++ tags.h | 14 + tests/autolink.t | 27 + tests/automatic.t | 28 + tests/backslash.t | 21 + tests/callbacks.t | 17 + tests/centered.t | 13 + tests/chrome.text | 13 + tests/code.t | 35 + tests/codeblock.t | 250 ++++ tests/compat.t | 34 + tests/crash.t | 103 ++ tests/data/README | 9 + tests/data/f01.html | 22 + tests/data/f01.text | 15 + tests/data/m_embed.html | 189 +++ tests/data/m_embed.text | 190 +++ tests/data/m_leading.html | 189 +++ tests/data/m_leading.text | 190 +++ tests/data/m_onechar.html | 189 +++ tests/data/m_onechar.text | 190 +++ tests/data/m_paired.html | 189 +++ tests/data/m_paired.text | 190 +++ tests/defects.t | 25 + tests/div.t | 54 + tests/dl.t | 101 ++ tests/e_url.t | 13 + tests/embedlinks.text | 9 + tests/emphasis.t | 19 + tests/exercisers/flags.c | 40 + tests/exercisers/make.include | 16 + tests/extrafootnotes.t | 140 +++ tests/flow.t | 33 + tests/footnotes.t | 33 + tests/functions.sh | 93 ++ tests/githubtags.t | 23 + tests/header.t | 26 + tests/html.t | 115 ++ tests/html5.t | 17 + tests/html_comment.t | 49 + tests/latex.t | 29 + tests/links.text | 14 + tests/linkyEA.t | 16 + tests/linkylinky.t | 137 ++ tests/linkypix.t | 33 + tests/list.t | 217 ++++ tests/list3deep.t | 38 + tests/misc.t | 12 + tests/muñoz.t | 144 +++ tests/pandoc.t | 91 ++ tests/para.t | 17 + tests/peculiarities.t | 77 ++ tests/pseudo.t | 20 + tests/reddit.t | 27 + tests/reparse.t | 14 + tests/safelinks.t | 18 + tests/schiraldi.t | 91 ++ tests/smarty.t | 25 + tests/snakepit.t | 34 + tests/strikethrough.t | 16 + tests/style.t | 36 + tests/superscript.t | 21 + tests/syntax.text | 897 +++++++++++++ tests/tables.t | 276 ++++ tests/tabstop.t | 48 + tests/toc.t | 118 ++ tests/xml.t | 18 + theme.1 | 160 +++ theme.c | 736 +++++++++++ toc.c | 221 ++++ tools/README | 17 + tools/branch.c | 27 + tools/checkbits.sh | 13 + tools/cols.c | 38 + tools/echo.c | 24 + tools/pandoc_headers.c | 98 ++ tools/rep.c | 84 ++ tools/space2nl.c | 16 + v2compat.c | 282 +++++ version.c.in | 15 + xml.c | 85 ++ xmlpage.c | 53 + 143 files changed, 21517 insertions(+), 2 deletions(-) create mode 100644 COPYRIGHT create mode 100644 CREDITS create mode 100644 Csio.c create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 Plan9/README.md create mode 100644 Plan9/markdown.1 create mode 100644 Plan9/markdown.2 create mode 100644 Plan9/markdown.6 create mode 100644 Plan9/mkfile create mode 100644 README create mode 100644 VERSION create mode 100644 amalloc.c create mode 100644 amalloc.h create mode 100644 basename.c create mode 100644 cmake/CMakeLists.txt create mode 100644 cmake/config.h.in create mode 100644 cmake/discount-config.cmake.in create mode 100755 configure.inc create mode 100755 configure.sh create mode 100644 css.c create mode 100644 cstring.h create mode 100644 docheader.c create mode 100644 dumptree.c create mode 100644 emmatch.c create mode 100644 flagprocs.c create mode 100644 flags.c create mode 100644 generate.c create mode 100644 gethopt.3 create mode 100644 gethopt.c create mode 100644 gethopt.h create mode 100644 github_flavoured.c create mode 100644 h1title.c create mode 100644 html5.c create mode 100644 libmarkdown.pc.in create mode 100644 main.c create mode 100644 makepage.1 create mode 100644 makepage.c create mode 100644 markdown.1 create mode 100644 markdown.3 create mode 100644 markdown.7 create mode 100644 markdown.c create mode 100644 markdown.h create mode 100644 mkd-callbacks.3 create mode 100644 mkd-extensions.7 create mode 100644 mkd-functions.3 create mode 100644 mkd-line.3 create mode 100644 mkd2html.1 create mode 100644 mkd2html.c create mode 100644 mkdio.c create mode 100644 mkdio.h.in create mode 100644 mktags.c create mode 100644 msvc/Makefile create mode 100644 msvc/README.md create mode 100644 msvc/config.h.vc create mode 100644 notspecial.c create mode 100644 pgm_options.c create mode 100644 pgm_options.h create mode 100644 resource.c create mode 100644 setup.c create mode 100644 tags.c create mode 100644 tags.h create mode 100644 tests/autolink.t create mode 100644 tests/automatic.t create mode 100644 tests/backslash.t create mode 100644 tests/callbacks.t create mode 100644 tests/centered.t create mode 100644 tests/chrome.text create mode 100644 tests/code.t create mode 100644 tests/codeblock.t create mode 100644 tests/compat.t create mode 100644 tests/crash.t create mode 100644 tests/data/README create mode 100644 tests/data/f01.html create mode 100644 tests/data/f01.text create mode 100644 tests/data/m_embed.html create mode 100644 tests/data/m_embed.text create mode 100644 tests/data/m_leading.html create mode 100644 tests/data/m_leading.text create mode 100644 tests/data/m_onechar.html create mode 100644 tests/data/m_onechar.text create mode 100644 tests/data/m_paired.html create mode 100644 tests/data/m_paired.text create mode 100644 tests/defects.t create mode 100644 tests/div.t create mode 100644 tests/dl.t create mode 100644 tests/e_url.t create mode 100644 tests/embedlinks.text create mode 100644 tests/emphasis.t create mode 100644 tests/exercisers/flags.c create mode 100644 tests/exercisers/make.include create mode 100644 tests/extrafootnotes.t create mode 100644 tests/flow.t create mode 100644 tests/footnotes.t create mode 100644 tests/functions.sh create mode 100644 tests/githubtags.t create mode 100644 tests/header.t create mode 100644 tests/html.t create mode 100644 tests/html5.t create mode 100644 tests/html_comment.t create mode 100644 tests/latex.t create mode 100644 tests/links.text create mode 100644 tests/linkyEA.t create mode 100644 tests/linkylinky.t create mode 100644 tests/linkypix.t create mode 100644 tests/list.t create mode 100644 tests/list3deep.t create mode 100644 tests/misc.t create mode 100644 tests/muñoz.t create mode 100644 tests/pandoc.t create mode 100644 tests/para.t create mode 100644 tests/peculiarities.t create mode 100644 tests/pseudo.t create mode 100644 tests/reddit.t create mode 100644 tests/reparse.t create mode 100644 tests/safelinks.t create mode 100644 tests/schiraldi.t create mode 100644 tests/smarty.t create mode 100644 tests/snakepit.t create mode 100644 tests/strikethrough.t create mode 100644 tests/style.t create mode 100644 tests/superscript.t create mode 100644 tests/syntax.text create mode 100644 tests/tables.t create mode 100644 tests/tabstop.t create mode 100644 tests/toc.t create mode 100644 tests/xml.t create mode 100644 theme.1 create mode 100644 theme.c create mode 100644 toc.c create mode 100644 tools/README create mode 100644 tools/branch.c create mode 100755 tools/checkbits.sh create mode 100644 tools/cols.c create mode 100644 tools/echo.c create mode 100644 tools/pandoc_headers.c create mode 100644 tools/rep.c create mode 100644 tools/space2nl.c create mode 100644 v2compat.c create mode 100644 version.c.in create mode 100644 xml.c create mode 100644 xmlpage.c diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..58f38d6 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,30 @@ +->Copyright (C) 2007-2024 Jessica Loren 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 works must retain the original copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the original copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither my name (Jessica L Parsons) nor the names of contributors to + this code may be used to endorse or promote products derived + from this work without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 +COPYRIGHT OWNER 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. + + diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..a806a53 --- /dev/null +++ b/CREDITS @@ -0,0 +1,35 @@ +Discount is primarily my work, but it has only reached the point +where it is via contributions, critiques, and bug reports from a +host of other people, some of which are listed before. If your +name isn't on this list, please remind me + -david parsons (orc@pell.portland.or.us) + + +Josh Wood -- Plan9 support. +Mike Schiraldi -- Reddit style automatic links, MANY MANY MANY + bug reports about boundary conditions and + places where I didn't get it right. +Jjgod Jiang -- Table of contents support. +Petite Abeille -- Many bug reports about places where I didn't + get it right. +Tim Channon -- inspiration for the `mkd_xhtmlpage()` function +Christian Herenz-- Many bug reports regarding my implementation of + `[]()` and `![]()` +A.S.Bradbury -- Portability bug reports for 64 bit systems. +Joyent -- Loan of a solaris box so I could get discount + working under solaris. +Ryan Tomayko -- Portability requests (and the rdiscount ruby + binding.) +yidabu -- feedback on the documentation, bug reports + against utf-8 support. +Pierre Joye -- bug reports, php discount binding. +Masayoshi Sekimura- perl discount binding. +Jeremy Hinegardner- bug reports about list handling. +Andrew White -- bug reports about the format of generated urls. +Steve Huff -- bug reports about Makefile portability (for Fink) +Ignacio Burgue?o-- bug reports about `>%class%` +Henrik Nyh -- bug reports about embedded html handling. +John J. Foerch -- bug reports about incorrect `–` and `—` + translations. + + diff --git a/Csio.c b/Csio.c new file mode 100644 index 0000000..112fd17 --- /dev/null +++ b/Csio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" + + +/* putc() into a cstring + */ +void +Csputc(int c, Cstring *iot) +{ + EXPAND(*iot) = c; +} + + +/* printf() into a cstring + */ +int +Csprintf(Cstring *iot, char *fmt, ...) +{ + va_list ptr; + int siz=100; + + do { + RESERVE(*iot, siz); + va_start(ptr, fmt); + siz = vsnprintf(T(*iot)+S(*iot), ALLOCATED(*iot)-S(*iot), fmt, ptr); + va_end(ptr); + } while ( siz > (ALLOCATED(*iot)-S(*iot)) ); + + S(*iot) += siz; + return siz; +} + + +/* write() into a cstring + */ +int +Cswrite(Cstring *iot, char *bfr, int size) +{ + RESERVE(*iot, size); + memcpy(T(*iot)+S(*iot), bfr, size); + S(*iot) += size; + return size; +} + + +/* reparse() into a cstring + */ +void +Csreparse(Cstring *iot, char *buf, int size, mkd_flag_t* flags) +{ + MMIOT f; + ___mkd_initmmiot(&f, 0, flags); + + ___mkd_reparse(buf, size, flags, &f, 0); + ___mkd_emblock(&f); + SUFFIX(*iot, T(f.out), S(f.out)); + ___mkd_freemmiot(&f, 0); +} diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2690641 --- /dev/null +++ b/INSTALL @@ -0,0 +1,67 @@ + + HOW TO BUILD AND INSTALL DISCOUNT + +1) Unpacking the distribution + +The DISCOUNT sources are distributed in tarballs. After extracting from +the tarball, you should end up with all the source and build files in the +directory + discount-(version) + +2) Installing the distribution + +DISCOUNT uses configure.sh to set itself up for compilation. To run +configure, just do ``./configure.sh'' and it will check your system for +build dependencies and build makefiles for you. If configure.sh finishes +without complaint, you can then do a ``make'' to compile everything and a +``make install'' to install the binaries. + +Configure.sh has a few options that can be set: + +--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 +--with-amalloc Use my paranoid malloc library to catch memory leaks +--shared Build shared libraries +--debian-glitch When mangling email addresses, do them deterministically + so the Debian regression tester won't complain +--pkg-config Build & install a pkg-config(1) .pc file for + the discount library. +--h1-title Have theme & mkd2html use the first h1 in a document + as the title if there's no pandoc header or title + specified on the command line. +--cxx-binding Wrap mkdio.h with (conditional) 'extern "C"' for c++ + binding. + +3) Testing + +``make test'' runs discount against a collection of test cases. + + +4) Installing sample programs and manpages + +The standard ``make install'' rule just installs the binaries. If you +want to install the sample programs, they are installed with +``make install.samples''; to install manpages, ``make install.man''. +A shortcut to install everything is ``make install.everything'' + + +5) Assorted platform gotchas + + 1. On NetBSD (version 8 for certain) running configure.sh by + itself will result in logging output being mixed in with diagnostic + output on the screen instead of having it written to config.log. + If, instead, you do `ksh ./configure.sh`, it will be much less + garbled (the shell defaults all fds > stderr to close on exec, + so my redirecting stdout fails after the first subprocess.) + 2. On 9Front (and maybe every other extant plan9 variant) the + system mkfile sets the `T' flag in CFLAGS; there are several + places where I typedef voids to opaque structure pointers and + this makes the build die when it attempts to link anything. + This was fixed by explicitly setting CFLAGS in Plan9/mkfile in + . diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..c50073f --- /dev/null +++ b/Makefile.in @@ -0,0 +1,208 @@ +CC=@CC@ +CPPFLAGS=@CPPFLAGS@ +CFLAGS=@CFLAGS@ +LDFLAGS=@LDFLAGS@ +AR=@AR@ +RANLIB=@RANLIB@ +INSTALL_PROGRAM=@INSTALL_PROGRAM@ +INSTALL_DIR=@INSTALL_DIR@ +INSTALL_DATA=@INSTALL_DATA@ + +BUILD=$(CC) -fPIC -I. $(CPPFLAGS) $(CFLAGS) +LINK=$(CC) -fPIC -L. $(LDFLAGS) + +.c.o: + $(BUILD) -c -o $@ $< + + +BINDIR=@exedir@ +MANDIR=@mandir@ +LIBDIR=@libdir@ +INCDIR=@prefix@/include +@MK_PKGCONFIG@PKGDIR=$(LIBDIR)/pkgconfig + +PGMS=markdown +SAMPLE_PGMS=mkd2html makepage +@THEME@SAMPLE_PGMS+= theme +MKDLIB=libmarkdown +OBJS=mkdio.o markdown.o dumptree.o generate.o \ + resource.o docheader.o version.o toc.o css.o \ + xml.o Csio.o xmlpage.o basename.o emmatch.o \ + github_flavoured.o setup.o tags.o html5.o \ + pgm_options.o flags.o v2compat.o flagprocs.o \ + @AMALLOC@ @H1TITLE@ +TESTFRAMEWORK=rep echo cols branch pandoc_headers space2nl + +# modules that markdown, makepage, mkd2html, &tc use +COMMON=gethopt.o notspecial.o + +MAN3PAGES=mkd-callbacks.3 mkd-functions.3 markdown.3 mkd-line.3 + +all: $(PGMS) $(SAMPLE_PGMS) $(TESTFRAMEWORK) all_subdirs + +install: $(PGMS) $(DESTDIR)$(BINDIR) $(DESTDIR)$(LIBDIR) $(DESTDIR)$(INCDIR) $(DESTDIR)$(PKGDIR) + $(INSTALL_PROGRAM) $(PGMS) $(DESTDIR)$(BINDIR) + ./librarian.sh install libmarkdown VERSION $(DESTDIR)$(LIBDIR) + $(INSTALL_DATA) mkdio.h $(DESTDIR)$(INCDIR) + @MK_PKGCONFIG@$(INSTALL_DATA) $(MKDLIB).pc $(DESTDIR)$(PKGDIR) + +install.everything: install install.samples install.man + +install.samples: $(SAMPLE_PGMS) install $(DESTDIR)$(BINDIR) + $(INSTALL_DIR) $(DESTDIR)$(MANDIR)/man1 + for x in $(SAMPLE_PGMS); do \ + $(INSTALL_PROGRAM) $$x $(DESTDIR)$(BINDIR)/$(SAMPLE_PFX)$$x; \ + $(INSTALL_DATA) $$x.1 $(DESTDIR)$(MANDIR)/man1/$(SAMPLE_PFX)$$x.1; \ + done + +install.man: + $(INSTALL_DIR) $(DESTDIR)$(MANDIR)/man3 + $(INSTALL_DATA) $(MAN3PAGES) $(DESTDIR)$(MANDIR)/man3 + for x in mkd_line mkd_generateline; do \ + ( echo '.\"' ; echo ".so man3/mkd-line.3" ) > $(DESTDIR)$(MANDIR)/man3/$$x.3;\ + done + for x in mkd_in mkd_string; do \ + ( echo '.\"' ; echo ".so man3/markdown.3" ) > $(DESTDIR)$(MANDIR)/man3/$$x.3;\ + done + for x in mkd_compile mkd_css mkd_generatecss mkd_generatehtml mkd_cleanup mkd_doc_title mkd_doc_author mkd_doc_date; do \ + ( echo '.\"' ; echo ".so man3/mkd-functions.3" ) > $(DESTDIR)$(MANDIR)/man3/$$x.3; \ + done + $(INSTALL_DIR) $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_DATA) markdown.7 mkd-extensions.7 $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_DIR) $(DESTDIR)$(MANDIR)/man1 + $(INSTALL_DATA) markdown.1 $(DESTDIR)$(MANDIR)/man1 + +install.everything: install install.man + +$(DESTDIR)$(BINDIR): + $(INSTALL_DIR) $(DESTDIR)$(BINDIR) + +$(DESTDIR)$(INCDIR): + $(INSTALL_DIR) $(DESTDIR)$(INCDIR) + +$(DESTDIR)$(LIBDIR): + $(INSTALL_DIR) $(DESTDIR)$(LIBDIR) + +@MK_PKGCONFIG@$(DESTDIR)$(PKGDIR): +@MK_PKGCONFIG@ $(INSTALL_DIR) $(DESTDIR)$(PKGDIR) + +version.o: version.c VERSION branch + $(BUILD) -DBRANCH=`./branch` -DVERSION=\"`cat VERSION`\" -c version.c + +VERSION: + @true + +tags.o: tags.c cstring.h tags.h blocktags + +blocktags: mktags + ./mktags > blocktags + +mktags: mktags.o + $(LINK) -o mktags mktags.o + +# example programs +@THEME@theme: theme.o $(COMMON) $(MKDLIB) mkdio.h +@THEME@ $(LINK) -o theme theme.o $(COMMON) -lmarkdown @LIBS@ + + +mkd2html: mkd2html.o $(MKDLIB) mkdio.h gethopt.h $(COMMON) + $(LINK) -o mkd2html mkd2html.o $(COMMON) -lmarkdown @LIBS@ + +markdown: main.o $(COMMON) $(MKDLIB) + $(LINK) -o markdown main.o $(COMMON) -lmarkdown @LIBS@ + +makepage.o: makepage.c mkdio.h + $(BUILD) -c makepage.c +makepage: makepage.o $(COMMON) $(MKDLIB) + $(LINK) -o makepage makepage.o $(COMMON) -lmarkdown @LIBS@ + +pgm_options.o: pgm_options.c mkdio.h config.h + $(BUILD) -c pgm_options.c + +notspecial.o: notspecial.c + $(BUILD) -c notspecial.c + +gethopt.o: gethopt.c + $(BUILD) -c gethopt.c + +main.o: main.c mkdio.h config.h + $(BUILD) -c main.c + +$(MKDLIB): $(OBJS) .libmarkdown + ./librarian.sh make $(MKDLIB) VERSION $(OBJS) + +.libmarkdown: $(OBJS) + touch $@ + +verify: echo tools/checkbits.sh verify_subdirs + @./echo -n "headers ... "; tools/checkbits.sh && echo "GOOD" + +test: $(PGMS) $(TESTFRAMEWORK) verify + @for x in $${TESTS:-tests/*.t}; do \ + @LD_LIBRARY_PATH@=. sh $$x || exit 1; \ + done + +pandoc_headers.o: tools/pandoc_headers.c config.h + $(BUILD) -c -o pandoc_headers.o tools/pandoc_headers.c +pandoc_headers: pandoc_headers.o $(COMMON) $(MKDLIB) + $(LINK) -o pandoc_headers pandoc_headers.o $(COMMON) -lmarkdown + +rep.o : tools/rep.c + $(BUILD) -c -o rep.o tools/rep.c +rep: rep.o + $(LINK) -o rep rep.o + +branch.o: tools/branch.c config.h + $(BUILD) -c -o branch.o tools/branch.c +branch: branch.o + $(LINK) -o branch branch.o + +cols.o: tools/cols.c config.h + $(BUILD) -c -o cols.o tools/cols.c +cols: cols.o + $(LINK) -o cols cols.o +space2nl.o: tools/space2nl.c config.h + $(BUILD) -c -o space2nl.o tools/space2nl.c +space2nl: space2nl.o + $(LINK) -o space2nl space2nl.o +echo.o: tools/echo.c config.h + $(BUILD) -c -o echo.o tools/echo.c +echo: echo.o + $(LINK) -o echo echo.o + +clean: clean_subdirs + rm -f $(PGMS) $(TESTFRAMEWORK) $(SAMPLE_PGMS) *.o + rm -f $(MKDLIB) `./librarian.sh files $(MKDLIB) VERSION` + +distclean spotless: clean + @DISTCLEAN@ @GENERATED_FILES@ @CONFIGURE_FILES@ ./mktags ./blocktags + +include tests/exercisers/make.include + +Csio.o: Csio.c cstring.h amalloc.h config.h markdown.h +amalloc.o: amalloc.c +basename.o: basename.c config.h cstring.h amalloc.h markdown.h +css.o: css.c config.h cstring.h amalloc.h markdown.h +docheader.o: docheader.c config.h cstring.h amalloc.h markdown.h +dumptree.o: dumptree.c markdown.h cstring.h amalloc.h config.h +emmatch.o: emmatch.c config.h cstring.h amalloc.h markdown.h +generate.o: generate.c config.h cstring.h amalloc.h markdown.h +main.o: main.c config.h amalloc.h +pgm_options.o: pgm_options.c pgm_options.h config.h amalloc.h +flagprocs.o: flagprocs.c pgm_options.h markdown.h config.h amalloc.h +makepage.o: makepage.c +markdown.o: markdown.c config.h cstring.h amalloc.h markdown.h +mkd2html.o: mkd2html.c config.h mkdio.h cstring.h amalloc.h +mkdio.o: mkdio.c config.h cstring.h amalloc.h markdown.h +resource.o: resource.c config.h cstring.h amalloc.h markdown.h +theme.o: theme.c config.h mkdio.h cstring.h amalloc.h +toc.o: toc.c config.h cstring.h amalloc.h markdown.h +version.o: version.c config.h +xml.o: xml.c config.h cstring.h amalloc.h markdown.h +xmlpage.o: xmlpage.c config.h cstring.h amalloc.h markdown.h +setup.o: setup.c config.h cstring.h amalloc.h markdown.h +github_flavoured.o: github_flavoured.c config.h cstring.h amalloc.h markdown.h +v2compat.o: v2compat.c config.h cstring.h amalloc.h markdown.h +gethopt.o: gethopt.c gethopt.h +h1title.o: h1title.c markdown.h +notspecial.o: notspecial.c config.h diff --git a/Plan9/README.md b/Plan9/README.md new file mode 100644 index 0000000..9d84423 --- /dev/null +++ b/Plan9/README.md @@ -0,0 +1,44 @@ +# *Discount* Markdown compiler on Plan 9 + +## Build + +### One line + + % mk install + % markdown -V + markdown: discount X.Y.Z GHC=INPUT + +### Stepwise + + % CONFIG='--with-tabstops=7' mk config + % mk + % mk test + % mk install + % markdown -V + markdown: discount X.Y.Z TAB=7 GHC=INPUT + +See `../configure.sh` and `../pgm_options.c` for other config options. + +### Other *mk*(1) targets + +* `clean`: Delete built objects from source directory. +* `nuke`: Delete built objects and generated configuration. +* `install.libs`: Discount includes a C library and header. +Installation is optional. Plan 9 binaries are statically linked. +* `install.man`: Add *markdown* in manual sections 1, 2, and 6. +* `install.progs`: Extra programs. *makepage* writes complete +XHTML documents, rather than fragments. *mkd2html* is similar, +but produces HTML. +* `installall`: Do all `install*` targets above. +* `uninstall`: Remove anything added by `install*` targets above. + +## Notes + +This is not a port from POSIX to native Plan 9 APIs. The supplied +`mkfile` drives Discount's own `../configure.sh` and the `../Makefile` +it generates through [Plan 9's ANSI/POSIX Environment (APE)][ape-paper] +(in [*pcc*(1)][pcc-man]) to build the Discount source, then copies +the results to locations appropriate for system-wide use on Plan 9. + +[ape-paper]: https://plan9.io/sys/doc/ape.html +[pcc-man]: https://plan9.io/magic/man2html/1/pcc diff --git a/Plan9/markdown.1 b/Plan9/markdown.1 new file mode 100644 index 0000000..b38947f --- /dev/null +++ b/Plan9/markdown.1 @@ -0,0 +1,169 @@ +.TH MARKDOWN 1 +.SH NAME +markdown \- convert Markdown text to HTML +.SH SYNOPSIS +.B markdown +[ +.B -dTV +] +[ +.BI -b " url-base +] +[ +.BI -F " bitmap +] +[ +.BI -f " flags +] +[ +.BI -o " ofile +] +[ +.BI -s " text +] +[ +.BI -t " text +] +[ +.I file +] +.SH DESCRIPTION +The +.I markdown +utility reads the +.IR Markdown (6)-formatted +.I file +(or standard input) and writes its +.SM HTML +fragment representation on standard output. +.PP +The options are: +.TF dfdoptions +.TP +.BI -b " url-base +Links in source begining with +.B / +will be prefixed with +.I url-base +in the output. +.TP +.B -d +Instead of printing an +.SM HTML +fragment, print a parse tree. +.TP +.BI -F " bitmap +Set translation flags. +.I Bitmap +is a bit map of the various configuration options described in +.IR markdown (2). +.TP +.BI -f " flags +Set or clear various translation +.IR flags , +described below. +.I Flags +are in a comma-delimited list, with an optional +.B + +(set) prefix on each flag. +.TP +.BI -o " ofile +Write the generated +.SM HTML +to +.IR ofile . +.TP +.BI -s " text +Use the +.IR markdown (2) +function to format the +.I text +on standard input. +.TP +.B -T +Under +.B -f +.BR toc , +print the table of contents as an unordered list before the usual +.SM HTML +output. +.TP +.BI -t " text +Use +.IR mkd_text +(in +.IR markdown (2)) +to format +.I text +instead of processing standard input with +.IR markdown . +.TP +.B -V +Show version number and configuration. If the version includes the string +.BR DL_TAG , +.I markdown +was configured with definition list support. If the version includes the string +.BR HEADER , +.I markdown +was configured to support pandoc header blocks. +.PD +.SS TRANSLATION FLAGS +The translation flags understood by +.B -f +are: +.TF \ noheader +.TP +.B noimage +Don't allow image tags. +.TP +.B nolinks +Don't allow links. +.TP +.B nohtml +Don't allow any embedded HTML. +.TP +.B cdata +Generate valid XML output. +.TP +.B noheader +Do not process pandoc headers. +.TP +.B notables +Do not process the syntax extension for tables. +.TP +.B tabstops +Use Markdown-standard 4-space tabstops. +.TP +.B strict +Disable superscript and relaxed emphasis. +.TP +.B relax +Enable superscript and relaxed emphasis (the default). +.TP +.B toc +Enable table of contents support, generated from headings (in +.IR markdown (6)) +in the source. +.TP +.B 1.0 +Revert to Markdown 1.0 compatibility. +.PD +.PP +For example, +.B -f nolinks,quot +tells +.I markdown +not to allow +.B +tags, and to expand double quotes. +.SH SOURCE +.B /sys/src/cmd/discount +.SH SEE ALSO +.IR markdown (2), +.IR markdown (6) +.PP +http://daringfireball.net/projects/markdown/, +``Markdown''. +.SH DIAGNOSTICS +.I Markdown +exits 0 on success and >0 if an error occurs. diff --git a/Plan9/markdown.2 b/Plan9/markdown.2 new file mode 100644 index 0000000..d5ee04c --- /dev/null +++ b/Plan9/markdown.2 @@ -0,0 +1,332 @@ +.TH MARKDOWN 2 +.SH NAME +mkd_in, mkd_string, markdown, mkd_compile, mkd_css, mkd_generatecss, +mkd_document, mkd_generatehtml, mkd_xhtmlpage, mkd_toc, mkd_generatetoc, +mkd_cleanup, mkd_doc_title, mkd_doc_author, mkd_doc_date, mkd_line, +mkd_generateline \- convert Markdown text to HTML +.SH SYNOPSIS +.ta \w'MMIOT* 'u +.B #include +.PP +.B +MMIOT* mkd_in(FILE *input, int flags) +.PP +.B +MMIOT* mkd_string(char *buf, int size, int flags) +.PP +.B +int markdown(MMIOT *doc, FILE *output, int flags) +.PP +.B +int mkd_compile(MMIOT *document, int flags) +.PP +.B +int mkd_css(MMIOT *document, char **doc) +.PP +.B +int mkd_generatecss(MMIOT *document, FILE *output) +.PP +.B +int mkd_document(MMIOT *document, char **doc) +.PP +.B +int mkd_generatehtml(MMIOT *document, FILE *output) +.PP +.B +int mkd_xhtmlpage(MMIOT *document, int flags, FILE *output) +.PP +.B +int mkd_toc(MMIOT *document, char **doc) +.PP +.B +int mkd_generatetoc(MMIOT *document, FILE *output) +.PP +.B +void mkd_cleanup(MMIOT*); +.PP +.B +char* mkd_doc_title(MMIOT*) +.PP +.B +char* mkd_doc_author(MMIOT*) +.PP +.B +char* mkd_doc_date(MMIOT*) +.PP +.B +int mkd_line(char *string, int size, char **doc, int flags) +.PP +.B +int mkd_generateline(char *string, int size, FILE *output, int flags) +.PD +.PP +.SH DESCRIPTION +These functions convert +.IR Markdown (6) +text into +.SM HTML +markup. +.PP +.I Mkd_in +reads the text referenced by pointer to +.B FILE +.I input +and returns a pointer to an +.B MMIOT +structure of the form expected by +.I markdown +and the other converters. +.I Mkd_string +accepts one +.I string +and returns a pointer to +.BR MMIOT . +.PP +After such preparation, +.I markdown +converts +.I doc +and writes the result to +.IR output , +while +.I mkd_compile +transforms +.I document +in-place. +.PP +One or more of the following +.I flags +(combined with +.BR OR ) +control +.IR markdown 's +processing of +.IR doc : +.TF MKD_NOIMAGE +.TP +.B MKD_NOIMAGE +Do not process +.B ![] +and remove +.B +tags from the output. +.TP +.B MKD_NOLINKS +Do not process +.B [] +and remove +.B +tags from the output. +.TP +.B MKD_NOPANTS +Suppress Smartypants-style replacement of quotes, dashes, or ellipses. +.TP +.B MKD_STRICT +Disable superscript and relaxed emphasis processing. +.TP +.B MKD_TAGTEXT +Process as inside an +.SM HTML +tag: no +.BR , +no +.BR , +no +.SM HTML +or +.B [] +expansion. +.TP +.B MKD_NO_EXT +Don't process pseudo-protocols (in +.IR markdown (6)). +.TP +.B MKD_CDATA +Generate code for +.SM XML +.B ![CDATA[...]] +element. +.TP +.B MKD_NOHEADER +Don't process Pandoc-style headers. +.TP +.B MKD_TABSTOP +When reading documents, expand tabs to 4 spaces, overriding any compile-time configuration. +.TP +.B MKD_TOC +Label headings for use with the +.I mkd_generatetoc +and +.I mkd_toc +functions. +.TP +.B MKD_1_COMPAT +MarkdownTest_1.0 compatibility. Trim trailing spaces from first line of code blocks and disable implicit reference links (in +.IR markdown (6)). +.TP +.B MKD_AUTOLINK +Greedy +.SM URL +generation. When set, any +.SM URL +is converted to a hyperlink, even those not encased in +.BR <> . +.TP +.B MKD_SAFELINK +Don't make hyperlinks from +.B [][] +links that have unknown +.SM URL +protocol types. +.TP +.B MKD_NOTABLES +Do not process the syntax extension for tables (in +.IR markdown (6)). +.TP +.B MKD_EMBED +All of +.BR MKD_NOLINKS , +.BR MKD_NOIMAGE , +and +.BR MKD_TAGTEXT . +.PD +.PP +This implementation supports +Pandoc-style +headers and inline +.SM CSS +.B +at the end of the line or at the beginning of a subsequent line. +.IP +Style blocks apply to the entire document regardless of where they are defined. +.TP +Image Dimensions +Image specification has been extended with an argument describing image dimensions: +.BI = height x width. +For an image 400 pixels high and 300 wide, the new syntax is: +.IP +.EX + ![Alt text](/path/to/image.jpg =400x300 "Title") +.EE +.TP +Pseudo-Protocols +Pseudo-protocols that may replace the common +.B http: +or +.B mailto: +have been added to the link syntax described above. +.IP +.BR abbr : +Text following is used as the +.B title +attribute of an +.B abbr +tag wrapping the link text. So +.B [LT](abbr:Link Text) +gives +.B LT. +.IP +.BR id : +The link text is marked up and written to the output, wrapped with +.B +and +.BR . +.IP +.BR class : + The link text is marked up and written to the output, wrapped with +.B +and +.BR . +.IP +.BR raw : +Text following is written to the output with no further processing. +The link text is discarded. +.TP +Alphabetic Lists +If +.I markdown +was configured with +.BR --enable-alpha-list , +.IP +.EX +a. this +b. is +c. an alphabetic +d. list +.EE +.IP +yields an +.SM HTML +.B ol +ordered list. +.TP +Definition Lists +If configured with +.BR --enable-dl-tag , +markup for definition lists is enabled. A definition list item is defined as +.IP +.EX +=term= + definition +.EE +.TP +Tables +Tables are specified with a pipe +.RB ( | ) +and dash +.RB ( - ) +marking. The markdown text +.IP +.EX +header0|header1 +-------|------- + textA|textB + textC|textD +.EE +.IP +will produce an +.SM HTML +.B table +of two columns and three rows. +A header row is designated by ``underlining'' with dashes. +Declare a column's alignment by affixing a colon +.RB ( : ) +to the left or right end of the dashes underlining its header. +In the output, this +yields the corresponding value for the +.B align +attribute on each +.B td +cell in the column. +A colon at both ends of a column's header dashes indicates center alignment. +.TP +Relaxed Emphasis +The rules for emphasis are changed so that a single +.B _ +will not count as an emphasis character in the middle of a word. +This is useful for documenting some code where +.B _ +appears frequently, and would normally require a backslash escape. +.PD +.SH SEE ALSO +.IR markdown (1), +.IR markdown (2) +.PP +http://daringfireball.net/projects/markdown/syntax/, +``Markdown: Syntax''. +.PP +http://daringfireball.net/projects/smartypants/, +``Smarty Pants''. +.PP +http://michelf.com/projects/php-markdown/extra/#table, +``PHP Markdown Extra: Tables''. diff --git a/Plan9/mkfile b/Plan9/mkfile new file mode 100644 index 0000000..5360a91 --- /dev/null +++ b/Plan9/mkfile @@ -0,0 +1,45 @@ +BIN=/$objtype/bin +CC='cc' +CFLAGS='-D_BSD_EXTENSION -D_C99_SNPRINTF_EXTENSION' + +markdown:V: ../markdown + +../markdown: ../config.h + ape/psh -c 'cd .. && make' + +test: markdown + ape/psh -c 'cd .. && make test' + +install:V: ../markdown + cp ../markdown $BIN/markdown + +install.progs:V: install + cp ../makepage $BIN/makepage + cp ../mkd2html $BIN/mkd2html + +install.libs:V: install + cp ../mkdio.h /sys/include/ape/mkdio.h + cp ../libmarkdown.a /$objtype/lib/ape/libmarkdown.a + +install.man:V: install + cp markdown.1 /sys/man/1/markdown + cp markdown.2 /sys/man/2/markdown + cp markdown.6 /sys/man/6/markdown + +installall:V: install.libs install.man install.progs + +uninstall:V: + rm -f $BIN/markdown $BIN/makepage $BIN/mkd2html + rm -f /sys/include/ape/mkdio.h /$objtype/lib/ape/libmarkdown.a + rm -f /sys/man/1/markdown /sys/man/2/markdown /sys/man/6/markdown + +../config.h: + ape/psh -c 'cd .. && ./configure.sh $CONFIG' + +config:V: ../config.h + +clean:V: + ape/psh -c 'cd .. && make clean' + +nuke:V: + ape/psh -c 'cd .. && make distclean' diff --git a/README b/README new file mode 100644 index 0000000..afbd0fd --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +DISCOUNT is a implementation of John Gruber & Aaron Swartz's + Markdown markup language. It implements, as far as I can tell, +all of the language as described in + +and passes the Markdown test suite at + + +DISCOUNT is free software written by Jessica L. Parsons +; it is released under a BSD-style license +that allows you to do as you wish with it as long as you don't +attempt to claim it as your own work. + +Most of the programs included in the DISCOUNT distribution have +manual pages describing how they work. + +The file INSTALL describes how to build and install discount diff --git a/README.md b/README.md index a192114..a1606ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ -# discount +# Discount v3 shared library + +It's fork of Jessica (ex- David) L. Parsons's Clang implementation of John Gruber & Aaron Swartz's Markdown markup language: https://github.com/Orc/discount + +Download page - https://www.pell.portland.or.us/~orc/Code/discount/downloads.html + +By default, discount v3 compiled as static library, but using the lib's API requires shared library. + +Here is the solution: + +1. Clone this repo + +``` +git clone https://gitlabor.ru/Datenlabor/discount + +``` + +2. Run configure + +``` + +./configure.sh + +``` + +3. If you are already have discount installed, clean it + +``` + +sudo make clean + +``` + +4. Run make and make install + +``` + +make + +make install + +``` -Discount v3 Markdown lib as shared library \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..cd0a3b6 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +3.0.1.2 diff --git a/amalloc.c b/amalloc.c new file mode 100644 index 0000000..9592988 --- /dev/null +++ b/amalloc.c @@ -0,0 +1,142 @@ +/* + * debugging malloc()/realloc()/calloc()/free() that attempts + * to keep track of just what's been allocated today. + */ + +#include +#include +#include "config.h" + +#define MAGIC 0x1f2e3d4c + +struct alist { int magic, size, index; int *end; struct alist *next, *last; }; + +static struct alist list = { 0, 0, 0, 0 }; + +static int mallocs=0; +static int reallocs=0; +static int frees=0; + +static int index = 0; + +static void +die(char *msg, int index) +{ + fprintf(stderr, msg, index); + abort(); +} + + +void * +acalloc(int count, int size) +{ + struct alist *ret; + + if ( size > 1 ) { + count *= size; + size = 1; + } + + if ( ret = calloc(count + sizeof(struct alist) + sizeof(int), size) ) { + ret->magic = MAGIC; + ret->size = size * count; + ret->index = index ++; + ret->end = (int*)(count + (char*) (ret + 1)); + *(ret->end) = ~MAGIC; + if ( list.next ) { + ret->next = list.next; + ret->last = &list; + ret->next->last = ret; + list.next = ret; + } + else { + ret->last = ret->next = &list; + list.next = list.last = ret; + } + ++mallocs; + return ret+1; + } + return 0; +} + + +void* +amalloc(int size) +{ + void *ret = acalloc(1, size); + + if ( ret ) { + /* explicitally fill the mallocated memory with a nonzero character */ + memset(ret, 0x8f, size); + } + return ret; +} + + +void +afree(void *ptr) +{ + struct alist *p2 = ((struct alist*)ptr)-1; + + if ( p2->magic == MAGIC ) { + if ( ! (p2->end && *(p2->end) == ~MAGIC) ) + die("goddam: corrupted memory block %d in free()!\n", p2->index); + p2->last->next = p2->next; + p2->next->last = p2->last; + ++frees; + free(p2); + } + else + free(ptr); +} + + +void * +arealloc(void *ptr, int size) +{ + struct alist *p2 = ((struct alist*)ptr)-1; + struct alist save; + + if ( p2->magic == MAGIC ) { + if ( ! (p2->end && *(p2->end) == ~MAGIC) ) + die("goddam: corrupted memory block %d in realloc()!\n", p2->index); + save.next = p2->next; + save.last = p2->last; + p2 = realloc(p2, sizeof(int) + sizeof(*p2) + size); + + if ( p2 ) { + p2->size = size; + p2->end = (int*)(size + (char*) (p2 + 1)); + *(p2->end) = ~MAGIC; + p2->next->last = p2; + p2->last->next = p2; + ++reallocs; + return p2+1; + } + else { + save.next->last = save.last; + save.last->next = save.next; + return 0; + } + } + return realloc(ptr, size); +} + + +void +adump() +{ + struct alist *p; + + + for ( p = list.next; p && (p != &list); p = p->next ) { + fprintf(stderr, "allocated: %d byte%s\n", p->size, (p->size==1) ? "" : "s"); + fprintf(stderr, " [%.*s]\n", p->size, (char*)(p+1)); + } + + if ( getenv("AMALLOC_STATISTICS") ) { + fprintf(stderr, "%d malloc%s\n", mallocs, (mallocs==1)?"":"s"); + fprintf(stderr, "%d realloc%s\n", reallocs, (reallocs==1)?"":"s"); + fprintf(stderr, "%d free%s\n", frees, (frees==1)?"":"s"); + } +} diff --git a/amalloc.h b/amalloc.h new file mode 100644 index 0000000..43ca985 --- /dev/null +++ b/amalloc.h @@ -0,0 +1,29 @@ +/* + * debugging malloc()/realloc()/calloc()/free() that attempts + * to keep track of just what's been allocated today. + */ +#ifndef AMALLOC_D +#define AMALLOC_D + +#include "config.h" + +#ifdef USE_AMALLOC + +extern void *amalloc(int); +extern void *acalloc(int,int); +extern void *arealloc(void*,int); +extern void afree(void*); +extern void adump(); + +#define malloc amalloc +#define calloc acalloc +#define realloc arealloc +#define free afree + +#else + +#define adump() (void)1 + +#endif + +#endif/*AMALLOC_D*/ diff --git a/basename.c b/basename.c new file mode 100644 index 0000000..2a1d225 --- /dev/null +++ b/basename.c @@ -0,0 +1,42 @@ +/* + * mkdio -- markdown front end input functions + * + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include "config.h" +#include +#include +#include + +#include "mkdio.h" +#include "cstring.h" +#include "amalloc.h" + +static char * +e_basename(const char *string, const int size, void *context) +{ + char *ret; + char *base = (char*)context; + + if ( base && string && (string[0] == '/') && (ret=malloc(strlen(base)+size+2)) ) { + strcpy(ret, base); + strncat(ret, string, size); + return ret; + } + return 0; +} + +static void +basename_free(char *p, int len, void *ctx) +{ + if ( p ) free(p); +} + +void +mkd_basename(MMIOT *document, char *base) +{ + if ( document && base ) + mkd_e_url(document, e_basename, (mkd_free_t)basename_free, base); +} diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..3a93b29 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,266 @@ +cmake_minimum_required(VERSION 2.8.12) + +project(DISCOUNT C) + +get_filename_component(_ROOT "${CMAKE_CURRENT_LIST_DIR}" PATH) + +file(READ "${_ROOT}/VERSION" ${PROJECT_NAME}_VERSION) +string(STRIP "${${PROJECT_NAME}_VERSION}" ${PROJECT_NAME}_VERSION) + +set(${PROJECT_NAME}_WITH_TABSTOPS "4" CACHE STRING + "Set tabstops to N characters (default is 4)") +set(TABSTOP "${${PROJECT_NAME}_WITH_TABSTOPS}") + +set(${PROJECT_NAME}_MAKE_INSTALL ON CACHE BOOL + "Set to OFF to disable install rules (default is ON)") + +set(${PROJECT_NAME}_INSTALL_SAMPLES OFF CACHE BOOL + "Set to ON to install sample programs (default is OFF)") + +set(${PROJECT_NAME}_ONLY_LIBRARY OFF CACHE BOOL + "Set to ON to only build markdown library (default is OFF)") + +set(${PROJECT_NAME}_CXX_BINDING OFF CACHE BOOL + "Set to ON to install header files with c++ wrappers (default is OFF)") + +# Check headers +include(CheckIncludeFile) +check_include_file(libgen.h HAVE_LIBGEN_H) +check_include_file(pwd.h HAVE_PWD_H) +check_include_file(alloca.h HAVE_ALLOCA_H) +check_include_file(malloc.h HAVE_MALLOC_H) +check_include_file(sys/stat.h HAVE_STAT) + +# Types detection (from configure.inc: AC_SCALAR_TYPES ()) +include(CheckTypeSize) +check_type_size("unsigned long" SIZEOF_ULONG BUILTIN_TYPES_ONLY) +check_type_size("unsigned int" SIZEOF_UINT BUILTIN_TYPES_ONLY) +check_type_size("unsigned short" SIZEOF_USHORT BUILTIN_TYPES_ONLY) + +if(SIZEOF_ULONG EQUAL 4) + set(DWORD "unsigned long") +elseif(SIZEOF_UINT EQUAL 4) + set(DWORD "unsigned int") +else() + message(FATAL_ERROR "Could not detect DWORD type") +endif() + +if(SIZEOF_UINT EQUAL 2) + set(WORD "unsigned int") +elseif(SIZEOF_USHORT EQUAL 2) + set(WORD "unsigned short") +else() + message(FATAL_ERROR "Could not detect WORD type") +endif() + +set(BYTE "unsigned char") + +# Check symbols +include(CheckSymbolExists) +foreach(_symbol + bzero + strcasecmp _stricmp + strncasecmp _strnicmp) + string(TOUPPER ${_symbol} _SYMBOL) + check_symbol_exists(${_symbol} string.h HAVE_${_SYMBOL}) +endforeach() +check_symbol_exists(random stdlib.h HAVE_RANDOM) +check_symbol_exists(srandom stdlib.h HAVE_SRANDOM) +check_symbol_exists(getpwuid pwd.h HAVE_GETPWUID) +check_symbol_exists(basename libgen.h HAVE_BASENAME) +check_symbol_exists(fchdir unistd.h HAVE_FCHDIR) +if(HAVE_STAT) + check_symbol_exists(S_ISCHR sys/stat.h HAVE_S_ISCHR) + check_symbol_exists(S_ISFIFO sys/stat.h HAVE_S_ISFIFO) + check_symbol_exists(S_ISSOCK sys/stat.h HAVE_S_ISSOCK) +endif() + +if(NOT HAVE_BZERO) + set(DEFINE_BZERO "#define bzero(p, n) memset(p, 0, n)") +endif() + +if(NOT HAVE_STRCASECMP) + if(HAVE__STRICMP) + set(DEFINE_STRCASECMP "#define strcasecmp _stricmp") + else() + set(DEFINE_STRCASECMP "#error The symbol strcasecmp is not defined.") + endif() +endif() + +if(NOT HAVE_STRNCASECMP) + if(HAVE__STRNICMP) + set(DEFINE_STRNCASECMP "#define strncasecmp _strnicmp") + else() + set(DEFINE_STRNCASECMP "#error The symbol strncasecmp is not defined.") + endif() +endif() + +if(NOT HAVE_S_ISCHR OR NOT HAVE_S_ISFIFO OR NOT HAVE_S_ISSOCK) + set(HAVE_STAT "") +endif() + +configure_file(config.h.in + "${CMAKE_CURRENT_BINARY_DIR}/config.h" + @ONLY) + +configure_file("${_ROOT}/version.c.in" + "${CMAKE_CURRENT_BINARY_DIR}/version.c" + @ONLY) +set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/version.c" APPEND PROPERTY COMPILE_DEFINITIONS + BRANCH="" + VERSION="${${PROJECT_NAME}_VERSION}") + +configure_file("${_ROOT}/mkdio.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" + @ONLY) +if(${PROJECT_NAME}_CXX_BINDING) + message(STATUS "Applying c++ glue to mkdio.h") + file(READ "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" _ROOT_MKDIO_H) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" "#ifdef __cplusplus\nextern \"C\" {\n#endif\n") + file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" "${_ROOT_MKDIO_H}") + file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" "#ifdef __cplusplus\n}\n#endif\n") +endif() + +add_executable(mktags + "${_ROOT}/mktags.c") + +set(BLOCKTAGS_FILE "${CMAKE_CURRENT_BINARY_DIR}/blocktags") +add_custom_command(OUTPUT "${BLOCKTAGS_FILE}" + COMMAND mktags > ${BLOCKTAGS_FILE} + WORKING_DIRECTORY "${_ROOT}") + +target_include_directories(mktags + PRIVATE + $ +) + +add_library(libmarkdown + "${_ROOT}/mkdio.c" + "${_ROOT}/markdown.c" + "${_ROOT}/dumptree.c" + "${_ROOT}/generate.c" + "${_ROOT}/resource.c" + "${_ROOT}/docheader.c" + "${CMAKE_CURRENT_BINARY_DIR}/version.c" + "${_ROOT}/toc.c" + "${_ROOT}/css.c" + "${_ROOT}/xml.c" + "${_ROOT}/Csio.c" + "${_ROOT}/xmlpage.c" + "${_ROOT}/basename.c" + "${_ROOT}/emmatch.c" + "${_ROOT}/github_flavoured.c" + "${_ROOT}/setup.c" + "${BLOCKTAGS_FILE}" + "${_ROOT}/tags.c" + "${_ROOT}/html5.c" + "${_ROOT}/v2compat.c" + "${_ROOT}/flagprocs.c" + "${_ROOT}/flags.c") + +set_target_properties(libmarkdown PROPERTIES + OUTPUT_NAME markdown) + +target_include_directories(libmarkdown + PUBLIC + $ + PRIVATE + $ +) + +if(NOT ${PROJECT_NAME}_ONLY_LIBRARY) + add_library(common OBJECT + "${_ROOT}/pgm_options.c" + "${_ROOT}/gethopt.c") + + target_include_directories(common + PRIVATE + $ + ) + + add_executable(markdown + "${_ROOT}/main.c" + $) + + target_link_libraries(markdown PRIVATE libmarkdown) + + add_executable(mkd2html + "${_ROOT}/mkd2html.c" + $ + "${_ROOT}/notspecial.c") + + target_link_libraries(mkd2html PRIVATE libmarkdown) + + add_executable(makepage + "${_ROOT}/makepage.c" + $) + + target_link_libraries(makepage PRIVATE libmarkdown) +endif() + +if(${PROJECT_NAME}_MAKE_INSTALL) + string(TOLOWER ${PROJECT_NAME} _PACKAGE_NAME) + include(GNUInstallDirs) + if(NOT DEFINED CMAKE_INSTALL_CMAKEDIR) + set(CMAKE_INSTALL_CMAKEDIR + "${CMAKE_INSTALL_LIBDIR}/cmake/${_PACKAGE_NAME}" + CACHE STRING "CMake packages") + endif() + if(NOT DEFINED CMAKE_INSTALL_PKGCONFIGDIR) + set(CMAKE_INSTALL_PKGCONFIGDIR + "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + CACHE STRING "The pkg-config packages") + endif() + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mkdio.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + target_include_directories(libmarkdown INTERFACE + $ + ) + set(_TARGETS libmarkdown) + if(NOT ${PROJECT_NAME}_ONLY_LIBRARY) + list(APPEND _TARGETS markdown) + endif() + if(${PROJECT_NAME}_INSTALL_SAMPLES) + list(APPEND _TARGETS mkd2html makepage) + endif() + install(TARGETS ${_TARGETS} EXPORT ${_PACKAGE_NAME}-targets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") + install(EXPORT ${_PACKAGE_NAME}-targets + NAMESPACE ${_PACKAGE_NAME}:: + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}") + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${_PACKAGE_NAME}-config-version.cmake" + VERSION ${${PROJECT_NAME}_VERSION} + COMPATIBILITY AnyNewerVersion + ) + configure_file("${CMAKE_CURRENT_LIST_DIR}/discount-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${_PACKAGE_NAME}-config.cmake" + @ONLY) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${_PACKAGE_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${_PACKAGE_NAME}-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}") + unset(_TARGETS) + unset(_PACKAGE_NAME) + set(prefix "${CMAKE_INSTALL_PREFIX}") + set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") + set(PACKAGE_NAME "libmarkdown") + set(PACKAGE_VERSION "${${PROJECT_NAME}_VERSION}") + set(LIBS) + configure_file("${_ROOT}/${PACKAGE_NAME}.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}.pc" + @ONLY) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}.pc" + DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}") + unset(prefix) + unset(libdir) + unset(PACKAGE_NAME) + unset(PACKAGE_VERSION) + unset(LIBS) +endif() + +unset(_ROOT) diff --git a/cmake/config.h.in b/cmake/config.h.in new file mode 100644 index 0000000..e630934 --- /dev/null +++ b/cmake/config.h.in @@ -0,0 +1,79 @@ +/* + * Pre-digested configuration header. + * Generated from cmake/config.h.in. + * Tested with MSVC, MinGW on Windows and with GCC on Linux. + * File prototype: msvc/config.h.vc. + */ + +#ifndef _CONFIG_D +#define _CONFIG_D 1 + +/* + * `discount` feature macros - we want them all! + */ +#ifndef WITH_ID_ANCHOR +#define WITH_ID_ANCHOR 1 +#endif +#ifndef WITH_FENCED_CODE +#define WITH_FENCED_CODE 1 +#endif +#ifndef WITH_GITHUB_TAGS +#define WITH_GITHUB_TAGS 1 +#endif +#ifndef USE_DISCOUNT_DL +#define USE_DISCOUNT_DL 1 +#endif +#ifndef USE_EXTRA_DL +#define USE_EXTRA_DL 1 +#endif + +#ifdef _MSC_VER + +/* + * The Visual C++ "C" compiler has a `__inline` keyword implemented + * in Visual Studio 2008 and later, see + * + */ +#if _MSC_VER >= 1500 /* VC 9.0, MSC_VER 15, Visual Studio 2008 */ +#define inline __inline +#else +#define inline +#endif /* _MSC_VER >= 1500 */ + +#endif /* _MSC_VER */ + +@DEFINE_BZERO@ +@DEFINE_STRCASECMP@ +@DEFINE_STRNCASECMP@ + +/* + * Beware of conflicts with , which typedef's these names. + */ +#ifndef WINVER +#define DWORD @DWORD@ +#define WORD @WORD@ +#define BYTE @BYTE@ +#endif + +#cmakedefine HAVE_PWD_H 1 +#cmakedefine HAVE_GETPWUID 1 + +#cmakedefine HAVE_LIBGEN_H 1 +#cmakedefine HAVE_BASENAME 1 + +#cmakedefine HAVE_RANDOM 1 +#cmakedefine HAVE_SRANDOM 1 + +#define INITRNG(x) srand((unsigned int)x) +#define COINTOSS() (rand()&1) + +#cmakedefine HAVE_FCHDIR 1 +#cmakedefine HAVE_ALLOCA_H 1 +#cmakedefine HAVE_MALLOC_H 1 +#cmakedefine HAVE_STAT 1 + +#define TABSTOP @TABSTOP@ + +#define DESTRUCTOR + +#endif /* _CONFIG_D */ diff --git a/cmake/discount-config.cmake.in b/cmake/discount-config.cmake.in new file mode 100644 index 0000000..3ead2cd --- /dev/null +++ b/cmake/discount-config.cmake.in @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/@_PACKAGE_NAME@-targets.cmake") diff --git a/configure.inc b/configure.inc new file mode 100755 index 0000000..a6b6eb0 --- /dev/null +++ b/configure.inc @@ -0,0 +1,2019 @@ +# Copyright (c) 1999-2024 Jessica L. 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 JESSICA L. 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 JESSICA L +# 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" + + +# remove files created by configure on abend +# +AC_EXIT() { + rm -f $__config_files + exit ${1:-1} +} + +__fail=AC_EXIT + + +if dirname B/A 2>/dev/null >/dev/null; then +__ac_dirname() { + dirname "$1" +} +else +__ac_dirname() { + echo "$1" | sed -e 's:/[^/]*$::' +} +fi + +__remove() { + if [ \( -x "$1" \) -a "$__MACOS_DSYM" ]; then + rm -rf "$1".dSYM + fi + rm -f "$@" +} + +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 +} + +# +# 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 ngc$$.o "$@" $LIBS 2>ngc$$.err; then + __remove ngc$$.o ngc$$.err + TLOG " (found)" + return 0 + fi + __remove ngc$$.o + TLOG " (not found)" + echo "test failed: command was $AC_CC -c -o ngc$$.o" "$@" $LIBS + echo "output:" + cat ngc$$.err + __remove ngc$$.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 () { + + AC_PROG_CPP + + for __hdr in "$@"; do + ( echo "/* AC_CHECK_HEADERS */" + echo "#include <${__hdr}>" ) > ngc$$.c + + LOGN "looking for header $__hdr" + + if $AC_CPP_FILTER ngc$$.c >/dev/null; then + AC_DEFINE 'HAVE_'`echo $__hdr | $AC_UPPERCASE | tr './' '__'` 1 + rc=0 + LOG " (found)" + else + rc=1 + LOG " (not found)" + fi + __remove ngc$$.c + done + # return code is only meaningful if one header is checked + return $rc +} + + +# +# emulate GNU autoconf's AC_CHECK_FUNCS function +# +AC_CHECK_FUNCS () { + AC_PROG_CC + + B=`echo "$1" | sed -e 's/(.*)//'` + + case "$B" in + "$1") F="$1()"; PROTO="$1(void)"; need_proto=1 ;; + *) F="$1" ; unset PROTO; unset need_proto ;; + esac + + shift + __remove ngc$$.c + + while [ "$1" ]; do + echo "#include <$1>" >> ngc$$.c + shift + done + + if [ "$need_proto" ]; then + echo "void $PROTO;" >> ngc$$.c + fi + + cat >> ngc$$.c << EOF +int main(void) +{ + + $F; +} +EOF + + LOGN "looking for the $B function" + + if $AC_CC $AC_CPPFLAGS $AC_CFLAGS $AC_LDFLAGS -o ngc$$ ngc$$.c $LIBS; then + AC_DEFINE `echo ${2:-HAVE_$B} | $AC_UPPERCASE` 1 + TLOG " (found)" + rc=0 + else + echo "offending command was:" + cat ngc$$.c + echo "$AC_CC $AC_CFLAGS $AC_LDFLAGS -o ngc$$ ngc$$.c $LIBS" + TLOG " (not found)" + rc=1 + fi + __remove ngc$$ ngc$$.c + return $rc +} + + +# +# check to see if some structure exists +# +# usage: AC_CHECK_STRUCT structure {include ...} +# +AC_CHECK_STRUCT () { + struct=$1 + shift + + __remove ngc$$.c + + for include in $*; do + echo "#include <$include>" >> ngc$$.c + done + + cat >> ngc$$.c << EOF +int main(void) +{ + struct $struct foo; +} +EOF + + LOGN "looking for struct $struct" + + if __MAKEDOTO ngc$$.c; then + AC_DEFINE HAVE_STRUCT_`echo ${struct} | $AC_UPPERCASE` + rc=0 + else + rc=1 + fi + __remove ngc$$.c + return $rc +} + + +# +# check to see if some type exists +# +# usage: AC_CHECK_TYPE type {include ...} +# +AC_CHECK_TYPE () { + type=$1 + shift + + __remove ngc$$.c + + for include in $*; do + echo "#include <$include>" >> ngc$$.c + done + + cat >> ngc$$.c << EOF +int main(void) +{ + $type foo; +} +EOF + + LOGN "looking for $type type" + + if __MAKEDOTO ngc$$.c; then + AC_DEFINE HAVE_`echo ${type} | $AC_UPPERCASE` + rc=0 + else + rc=1 + fi + __remove 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 + + __remove ngc$$.c + + for include in $*;do + echo "#include <$include>" >> ngc$$.c + done + + cat >> ngc$$.c << EOF +int main(void) +{ + struct $struct foo; + + foo.$field; +} +EOF + + LOGN "checking that struct $struct has a $field field" + + if __MAKEDOTO ngc$$.c; then + # HAVE_STRUCT_ is for Gnu configure compatability + AC_DEFINE HAVE_STRUCT_`echo ${struct}_$field | $AC_UPPERCASE` + rc=0 + else + rc=1 + fi + __remove ngc$$.c + return $rc +} + + +# +# check that the C compiler works +# +AC_PROG_CC () { + test "$AC_CC" && return 0 + + cat > ngc$$.c << \EOF +void +say(char*); +int main(void) +{ + say("hello, sailor"); +} +EOF + cat > ngf$$.c << \EOF +#include +void +say(char* message) +{ + puts(message); +} +EOF + + TLOGN "checking the C compiler" + + unset AC_CFLAGS AC_LDFLAGS __MACOS_DSYM + + 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 ngc$$ ngc$$.c ngf$$.c + status=$? + echo "compile status = $status" + + TLOGN " ($AC_CC)" + + if [ $status -eq 0 ]; then + if $AC_CC -x c /dev/null -dM -E 2>&1 | grep '__clang__' >/dev/null; then + TLOG " yuck, you're using clang" + IS_CLANG=T + IS_BROKEN_CC=T + elif $AC_CC -v 2>&1 | grep 'gcc version' >/dev/null; then + TLOG " oh ick, it looks like gcc" + IS_GCC=T + IS_BROKEN_CC=T + else + TLOG " ok" + fi + + # 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 -c -o ngc$$.o ngc$$.c ; then + AC_CFLAGS=${CFLAGS:-"-g"} + test "$CFLAGS" && TLOG "CFLAGS=\"${CFLAGS}\" are okay" + elif [ "$CFLAGS" ]; then + TLOG "ignoring bogus CFLAGS=\"${CFLAGS}\"" + fi + else + AC_CFLAGS=-g + fi + if [ "$LDFLAGS" ]; then + test "$LDFLAGS" && echo "validating LDFLAGS=${LDFLAGS}" + if $AC_CC $CFLAGS $LDFLAGS -o ngc$$ ngc$$.c ngf$$.c; 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 + + # macos-specific(?) test for .dSYM resource directories + $AC_CC $AC_CFLAGS $AC_LDFLAGS -o ngc$$ ngc$$.c ngf$$.c + ls -dl ngc$$* + test -d ngc$$.dSYM && __MACOS_DSYM=1 + + else + AC_FAIL " does not compile code properly" + fi + + __remove ngc$$ ngc$$.c ngc$$.o ngf$$.o ngf$$.c + + 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 + for program in $*; do + case "$program" in + /*) if [ $__mode $program -a -f $program ]; then + echo $program + return + fi ;; + *) + oldifs="$IFS" + IFS=":" + for x in $path; do + if [ $__mode $x/$program -a -f $x/$program ]; then + echo $x/$program + IFS="$oldifs" + return + fi + done + IFS="$oldifs" ;; + esac + done +} + + +# +# 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" | $AC_UPPERCASE` + 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.sed" + rm -f $__config_files + __cwd=`pwd` + exec 5>&1 1>"$__cwd"/config.log 2>&1 + AC_CONFIGURE_FOR=__AC_`echo $1 | sed -e 's/\..$//' | $AC_UPPERCASE | 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]" + _MK_LIBRARIAN=Y + + 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_TR + + ac_os=`uname -s` + _os=`echo $ac_os | $AC_UPPERCASE | sed -e 's/[^A-Z0-9_].*$//'` + 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() { + acl_SRC=$1 + shift + + # first see if the function can be found in any of the + # current libraries + LOGN "Looking for the ${acl_SRC} function" + if AC_QUIET AC_CHECK_FUNCS $acl_SRC; then + AC_DEFINE HAVE_LIB`echo $1 | sed -e 's/-l//' | $AC_UPPERCASE` + LOG "(found)" + return 0 + fi + + # then search through the supplied list of libraries + acl_libs="$LIBS" + for x in "$@"; do + LIBS="$acl_libs $x" + if AC_QUIET AC_CHECK_FUNCS $SRC; then + AC_DEFINE HAVE_LIB`echo $1 | sed -e 's/-l//' | $AC_UPPERCASE` + LOG " (in $x)" + return 0 + fi + done + LOG " (not found)" + LIBS="$acl_libs" # reset LIBS if we couldn't find anything + 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 + TLOG "($DEST)" + + if AC_LIBRARY yywrap -ll -lfl; then + return 0 + else + TLOG "(no lex library found)" + return 1 + fi +} + + +# +# 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 looks for a program +# +AC_PROG () { + PN=`basename $1 | $AC_UPPERCASE | tr -dc $AC_UPPER_PAT` + + if set | grep -v PROG_$PN >/dev/null; then + TLOGN "looking for $1" + + __pgm=`eval echo \\$$PN` + if [ "$__pgm" ]; then + TLOGN " (defined as $__pgm)" + DEST=`acLookFor $__pgm` + else + DEST=`acLookFor $1` + fi + + if [ "$DEST" ]; then + eval PROG_$PN="$DEST" + AC_SUB $PN $DEST + TLOG " ($DEST)" + return 0 + fi + AC_SUB $PN true + TLOG " (not found)" + return 1 + fi +} + + +# +# AC_PROG_LN_S checks to see if ln exists, and, if so, if ln -s works +# +AC_PROG_LN_S () { + test "$PROG_FIND" || AC_PROG_FIND + + test "$PROG_LN_S" && return 0 + + TLOGN "looking for \"ln -s\"" + DEST=`acLookFor ln` + + if [ "$DEST" ]; then + rm -f /tmp/b$$ + $DEST -s /tmp/a$$ /tmp/b$$ + if [ "`$PROG_FIND /tmp/b$$ -type l -print`" ]; then + TLOG " ($DEST)" + echo "$DEST exists, and ln -s works" + PROG_LN_S="$DEST -s" + 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 "$PROG_FIND"; then + MF_PATH_INCLUDE FIND find + rc=$? + PROG_FIND=$CF_FIND + 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 $? +} + + +# +# check for the existence of __attribute__((__noreturn__)) +# +AC_CHECK_NORETURN() { + AC_PROG_CC + AC_CHECK_ATTRIBUTE noreturn +} + +AC_CHECK_ATTRIBUTE() { + acca_what=`echo $1 | $AC_UPPERCASE` + + echo "extern int thing __attribute__((__"$1"__));" > ngc$$.c + + TLOGN "Checking __attribute__((__${1}__)) " + if $AC_CC -c ngc$$.c; then + TLOG "(yes)" + AC_DEFINE $acca_what ' __attribute__((__'$1'__))' + else + TLOG "(no)" + AC_DEFINE $acca_what '/**/' + fi + rm -f ngc$$.o ngc$$.c +} + + +# +# AC_C_CONST checks to see if the compiler supports the const keyword +# +AC_C_CONST () { + cat > ngc$$.c << EOF +const char me=1; +EOF + LOGN "checking for \"const\" keyword" + + if __MAKEDOTO ngc$$.c; then + rc=0 + else + AC_DEFINE 'const' '/**/' + rc=1 + fi + __remove ngc$$.c + return $rc +} + + +# +# AC_C_VOLATILE checks to see if the compiler supports the volatile keyword +# +AC_C_VOLATILE () { + echo 'void f(void) { volatile char me=1; }' > ngc$$.c + LOGN "checking for \"volatile\" keyword" + + if __MAKEDOTO ngc$$.c; then + rc=0 + else + AC_DEFINE 'volatile' '/**/' + rc=1 + fi + __remove ngc$$.c + return $rc +} + + +# +# AC_C_INLINE checks to see if compiler supports the inline keyword +# +AC_C_INLINE() { + echo 'inline int foo(void) { return 1; }' > ngc$$.c + LOGN 'Checking for "inline" keyword' + if __MAKEDOTO ngc$$.c; then + rc=0 + else + AC_DEFINE inline '/**/' + rc=1 + fi + __remove ngc$$.c + return $rc +} + + +# +# AC_WHATIS tries to print out the value of a macro +# +AC_WHATIS() { + MODE=$1 # what it should be (string,int,char) + shift + MACRO=$1 # the macro name + shift + + case "$MODE" in + string) + __fmt='%s' ;; + int) + __fmt='%d' ;; + char) + __fmt='%c' ;; + *) LOG "AC_WHATIS $MODE $MACRO -- mode isn't string, int, or char" + return 1 ;; + esac + + ( echo '#include ' + + for x in "$@"; do + echo "#include <${x}>" + done + + echo "int main(void) { printf(\"${MACRO}=\\\"${__fmt}\\\"\\n\", ${MACRO}); }" ) > _ngc$$.c + + if $AC_CC $AC_CFLAGS -o _ngc$$ _ngc$$.c; then + ./_ngc$$ + rc=0 + else + rc=1 + fi + rm -f _ngc$$ _ngc$$.c + return $rc +} + + +# +# AC_SCALAR_TYPES checks to see if the compiler can generate 2 and 4 byte ints. +# +AC_SCALAR_TYPES () { + + rc=1 + LOGN "defining WORD & DWORD scalar types" + +# if AC_QUIET AC_CHECK_HEADERS WinDef.h; then +# # windows machine; BYTE, WORD, DWORD already +# # defined +# echo "#include " >> "$__cwd"/config.h +# TLOG " (defined in WinDef.h)" +# return 0 +# fi + + # try first to define them with the (allegedly) standard + # unsigned scalar types + # + unset __i; + if AC_QUIET AC_CHECK_HEADERS inttypes.h; then + __i=inttypes.h + elif AC_QUIET AC_CHECK_HEADERS stdint.h; then + __i=stdint.h + fi + if AC_QUIET AC_CHECK_TYPE uint32_t $__i && \ + AC_QUIET AC_CHECK_TYPE uint16_t $__i && \ + AC_QUIET AC_CHECK_TYPE uint8_t $__i; then + + while [ $# -gt 0 ]; do + case "$1" in + sub) ( if [ -z "$__i" ] ; then + echo "s:@SCALAR_HEADER_INCLUDE@::g" + else + echo "s:@SCALAR_HEADER_INCLUDE@:#include <$__i>:g" + fi + echo "s:@DWORD@:uint32_t:g" + echo "s:@WORD@:uint16_t:g" + echo "s:@BYTE@:uint8_t:g" ) >> "$__cwd"/config.sub + ;; + *) ( echo "#define DWORD uint32_t" + echo "#define WORD uint16_t" + echo "#define BYTE uint8_t" ) >> "$__cwd"/config.h + ;; + esac + shift + done + TLOG " (using standard types ${__i:+in <$__i>})" + return 0 + fi + + # and if that fails do a brute-force program that does sizeof()ication + # to figure things out + # + cat > ngc$$.c << EOF +#include +#include + +int pound_define = 1; + +void +say(char *w, char *v) +{ + printf(pound_define ? "#define %s %s\n" + : "s:@%s@:%s:g\n", w, v); +} + +int +main(int argc, char **argv) +{ + unsigned long v_long; + unsigned int v_int; + unsigned short v_short; + + if ( argc > 1 && strcmp(argv[1], "sub") == 0 ) + pound_define = 0; + + if (sizeof v_long == 4) + say("DWORD", "unsigned long"); + else if (sizeof v_int == 4) + say("DWORD", "unsigned int"); + else + return 1; + + if (sizeof v_int == 2) + say("WORD", "unsigned int"); + else if (sizeof v_short == 2) + say("WORD", "unsigned short"); + else + return 2; + + say("BYTE", "unsigned char"); + fprintf(stderr, "OK!"); + return 0; +} +EOF + + if $AC_CC ngc$$.c -o ngc$$; then + if [ $# -gt 0 ]; then + while [ "$1" ]; do + case "$1" in + sub)if ./ngc$$ sub >> "$__cwd"/config.sub; then + echo "s:@SCALAR_HEADER_INCLUDE@::g" >> "$__cwd"/config.sub + rc=0 + fi;; + *) if ./ngc$$ >> "$__cwd"/config.h; then + rc=0 + fi ;; + esac + shift + done + elif ./ngc$$ >> "$__cwd"/config.h; then + rc=0 + fi + if [ "$rc" != 0 ]; then + if ./ngc$$ >> "$__cwd"/config.h; then + rc=0 + fi + fi + fi + __remove ngc$$ ngc$$.c + case "$rc" in + 0) TLOG "" ;; + *) AC_FAIL " ** FAILED **" ;; + esac + return $rc +} + + +# +# AC_OUTPUT generates makefiles from makefile.in's +# +AC_OUTPUT () { + + cd "$__cwd" + AC_SUB 'LIBS' "$LIBS" + + if test "$__MACOS_DSYM"; then + # deal with extra OSX droppings, if they exist + AC_SUB 'DISTCLEAN' 'rm -fr' + AC_SUB 'GENERATED_FILES' "*.dSYM $*" + else + AC_SUB 'DISTCLEAN' 'rm -f' + AC_SUB 'GENERATED_FILES' "$*" + fi + + AC_SUB 'CC' "$AC_CC" + AC_SUB 'CFLAGS' "$AC_CFLAGS" + AC_SUB 'LDFLAGS' "$AC_LDFLAGS" + AC_SUB 'CPPFLAGS' "$CPPFLAGS" + AC_SUB 'srcdir' "$AC_SRCDIR" + AC_SUB 'prefix' "$AC_PREFIX" + AC_SUB 'exedir' "$AC_EXECDIR" + AC_SUB 'bindir' "$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 [ "$_MK_LIBRARIAN" ] && echo "$__config_files" | grep -v librarian.sh >/dev/null; then + # write a librarian that works with static libraries + if AC_PROG_LN_S ; then + __dolink=$PROG_LN_S + elif AC_PROG ln; then + __dolink=$PROG_LN + elif AC_PROG cp; then + __dolink=$PROG_CP + else + __dolink=: + fi + AC_PROG ar + AC_PROG ranlib + AC_SUB LD_LIBRARY_PATH HERE + __config_files="$__config_files librarian.sh" + cat > librarian.sh << EOF +#! /bin/sh +# +# Build static libraries, hiding (some) ickiness from the makefile + +ACTION=\$1; shift +LIBRARY=\$1; shift +VERSION=\$1; shift + +case "\$ACTION" in +make) # first strip out any libraries that might + # be passed in on the object line + objs= + for x in "\$@"; do + case "\$x" in + -*) ;; + *) objs="\$objs \$x" ;; + esac + done + ${PROG_AR} crv \$LIBRARY.a \$objs + ${PROG_RANLIB} \$LIBRARY.a + rm -f \$LIBRARY + ${__dolink} \$LIBRARY.a \$LIBRARY + ;; +files) echo "\${LIBRARY}.a" + ;; +install)$PROG_INSTALL -m 644 \${LIBRARY}.a \$1 + ;; +esac +EOF + chmod +x librarian.sh + fi + + AC_SUB 'CONFIGURE_FILES' "$__config_files config.log" + + 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}} ${LDFLAGS:+LDFLAGS=${Q}${LDFLAGS}${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 > ngc$$.c +#include +#include +#include +#include + +int main(void) +{ + int x = open("ngc$$.c", O_RDWR, 0666); + int y = open("ngc$$.c", O_RDWR, 0666); + + alarm(1); + if (flock(x, LOCK_EX) != 0) + exit(1); + if (flock(y, LOCK_EX|LOCK_NB) == 0) + exit(1); + exit(0); +} +EOF + + LOGN "checking flock() sanity" + HAS_FLOCK=0 + if $AC_CC -o ngc$$ ngc$$.c ; then + if ./ngc$$ ; then + LOG " (good)" + HAS_FLOCK=1 + AC_DEFINE HAS_FLOCK + else + LOG " (bad)" + fi + else + LOG " (not found)" + fi + + __remove ngc$$ ngc$$.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 "looking for the Berkeley resolver library" + + __ACR_rc=0 + + cat > ngc$$.c << EOF +#include +#include +#include +#include + +int main(void) +{ + char bfr[256]; + + res_init(); + res_query("hello", C_IN, T_A, bfr, sizeof bfr); +} +EOF + + if $AC_CC -o ngc$$ ngc$$.c; then + TLOG " (found)" + elif $AC_CC -o ngc$$ ngc$$.c -lresolv; then + TLOG " (yes, with -lresolv)" + LIBS="$LIBS -lresolv" + elif $AC_CC -DBIND_8_COMPAT -o ngc$$ ngc$$.c; then + TLOG " (yes, with BIND_8_COMPAT)" + AC_DEFINE BIND_8_COMPAT 1 + elif $AC_CC -DBIND_8_COMPAT -o ngc$$ ngc$$.c -lresolv; then + TLOG " (yes, with BIND_8_COMPAT & -lresolv)" + LIBS="$LIBS -lresolv" + AC_DEFINE BIND_8_COMPAT 1 + else + TLOG " (not found)" + __ACR_rc=1 + fi + __remove ngc$$ ngc$$.c + return $__ACR_rc +} + + +# +# AC_CHECK_ALLOCA looks for alloca +# +AC_CHECK_ALLOCA () { + + AC_PROG_CC + AC_CHECK_HEADERS stdlib.h + + cat - > ngc$$.c << EOF +#if T +# include +#else +# include +#endif +int main(void) +{ + alloca(10); +} +EOF + + LOGN "looking for the alloca function" + if $AC_CC -DT ngc$$.c -o ngc$$; then + AC_DEFINE 'HAVE_ALLOCA_H' 1 + status=0 + TLOG " (found in alloca.h)" + elif $AC_CC ngc$$.c -o ngc$$; then + TLOG " (found)" + status=0 + else + TLOG " (not found)" + status=1 + fi + __remove ngc$$ ngc$$.c + return $status + +} + + +# +# AC_CHECK_BASENAME looks for a copy of basename that does NOT use +# a local static buffer to hold results in. +# +AC_CHECK_BASENAME() { + TLOGN "looking for a reentrant basename " + + cat > ngc$$.c << EOF +#include + +extern char *basename(char*); + +int main(void) +{ + char *a = basename("/a/test"); + char *b = basename("/a/nother"); + + return (strcmp(a,b) != 0) ? 0 : 1; + +} +EOF + + if $AC_CC -o ngc$$ ngc$$.c $LIBS; then + if ./ngc$$; then + TLOG "(found)" + AC_DEFINE 'HAVE_BASENAME' 1 + AC_CHECK_HEADERS libgen.h + else + TLOG "(broken)" + fi + else + TLOG "(not found)" + fi + __remove ngc$$ ngc$$.c +} + +# +# AC_COMPILER_PIC checks for the compiler option to produce position independent +# code. At the moment we assume gcc semantics. +# +AC_COMPILER_PIC () { + AC_PROG_CC + + LOGN "checking for C compiler option to produce PIC " + echo "int some_variable = 0;" > ngc$$.c + + if $AC_CC -c -fPIC -o ngc$$ ngc$$.c $LIBS; then + AC_CFLAGS="$AC_CFLAGS -fPIC" + LOG "(-fPIC)" + __rc=0 + else + LOG "(none)" + __rc=1 + fi + __remove ngc$$ ngc$$.c + return $__rc +} + + +# generate a macosX librarian +# +__AC_MACOS_LIBRARIAN() { + AC_SUB LD_LIBRARY_PATH DYLD_LIBRARY_PATH + __config_files="$__config_files librarian.sh" + cat > librarian.sh << EOF +#! /bin/sh +# +# Build MacOS shared libraries, hiding (some) ickiness from the makefile + +ACTION=\$1; shift +LIBRARY=\$1; shift + +eval \`awk -F. '{ printf "MAJOR=%d\n", \$1; + printf "VERSION=%d.%d.%d\n", \$1, \$2, \$3; }' \$1\` +shift + +LIBNAME=\$LIBRARY.dylib +FULLNAME=\$LIBNAME + +case "\$ACTION" in +make) FLAGS="$AC_CFLAGS $AC_LDFLAGS -dynamiclib" + VFLAGS="-current_version \$VERSION -compatibility_version \$MAJOR" + + rm -f \$LIBRARY + $AC_CC \$FLAGS \$VFLAGS -o \$FULLNAME "\$@" || exit \$? + $PROG_LN_S \$FULLNAME \$LIBRARY + ;; +files) echo "\$FULLNAME" + ;; +install)$PROG_INSTALL -c \$FULLNAME "\$1" + ;; +esac +EOF + chmod +x librarian.sh +} + + +# Generate an ELF librarian (for Linux, freebsd) +# +__AC_ELF_LIBRARIAN() { + AC_SUB LD_LIBRARY_PATH LD_LIBRARY_PATH + # -Wl option probably works, but be paranoid anyway + _VFLAGS="$AC_PICFLAG -shared -Wl,-soname,ngc$$.so.1" + if $AC_CC $_VFLAGS -o ngc$$.so ngc$$.c; then + USE_SONAME=T + fi + LDCONFIG=`AC_PATH=/sbin:/usr/sbin:/usr/local/sbin acLookFor ldconfig` + + if [ "$LDCONFIG" ]; then + case `uname -s 2>/dev/null | $AC_UPPERCASE` in + *BSD) # *BSD ldconfig, when passed a directory, blows away the + # ld.so hints file and replaces it with one that's just + # the files in the library. It needs a `-m` flag to + # tell it to merge the new entries with the old + LDCONFIG="$LDCONFIG -m" ;; + esac + fi + + __config_files="$__config_files librarian.sh" + cat > librarian.sh << EOF +#! /bin/sh +# +# Build ELF shared libraries, hiding (some) ickiness from the makefile + +ACTION=\$1; shift +LIBRARY=\$1; shift + +eval \`awk -F. '{ printf "MAJOR=%d\n", \$1; + printf "VERSION=%d.%d.%d\n", \$1, \$2, \$3; }' \$1\` +shift + +LIBNAME=\$LIBRARY.so +FULLNAME=\$LIBNAME.\$VERSION + +case "\$ACTION" in +make) FLAGS="$AC_CFLAGS $AC_LDFLAGS -shared" + unset VFLAGS + test "$USE_SONAME" && VFLAGS="-Wl,-soname,\$LIBNAME.\$MAJOR" + + rm -f \$LIBRARY \$LIBNAME \$LIBNAME.\$MAJOR + $AC_CC \$FLAGS \$VFLAGS -o \$FULLNAME "\$@" || exit \$? + $PROG_LN_S \$FULLNAME \$LIBRARY + $PROG_LN_S \$FULLNAME \$LIBNAME + $PROG_LN_S \$FULLNAME \$LIBNAME.\$MAJOR + ;; +files) echo "\$FULLNAME" "\$LIBNAME" "\$LIBNAME.\$MAJOR" + ;; +install)$PROG_INSTALL -c \$FULLNAME "\$1" + $PROG_LN_S -f \$FULLNAME \$1/\$LIBNAME.\$MAJOR + $PROG_LN_S -f \$FULLNAME \$1/\$LIBNAME +EOF + if [ "$LDCONFIG" -a -z "$CONTAINER" ]; then + echo ' '$LDCONFIG '"$1"' >> librarian.sh + fi + + cat >> librarian.sh << EOF + ;; +esac +EOF + chmod +x librarian.sh +} + + +# +# AC_CC_SHLIBS checks if the C compiler can produce shared libraries +# and if it can writes a librarian that handles those libraries for us. +# +AC_CC_SHLIBS () { + AC_PROG_CC || AC_FAIL "Need a C compiler to build shared libraries" + AC_PROG_LN_S || AC_FAIL "Need to be able to make symbolic links for shared libraries" + AC_PROG_INSTALL || AC_FAIL "Need an install program to install shared libraries" + + LOGN "checking whether the C compiler can build shared libraries " + + echo "int some_variable = 0;" > ngc$$.c + + _MK_LIBRARIAN= + if uname -a | grep Darwin >/dev/null; then + # Claims to be macos? + if $AC_CC $AC_PICFLAG -dynamiclib -o ngc$$.so ngc$$.c; then + __AC_MACOS_LIBRARIAN + + LOG "(yes; macos dylib)" + __rc=0 + else + LOG "(no)" + __rc=1 + fi + elif $AC_CC $AC_PICFLAG -shared -o ngc$$.so ngc$$.c; then + __AC_ELF_LIBRARIAN + LOG "(yes; -shared)" + __rc=0 + else + _MK_LIBRARIAN=Y + LOG "(no)" + __rc=1 + fi + + __remove ngc$$.so ngc$$.c + + return $__rc +} + + +# +# AC_PROG_INSTALL finds the install program and guesses whether it's a +# Berkeley or GNU install program +# +AC_PROG_INSTALL () { + + if [ $PROG_INSTALL ]; then return; fi + + DEST=`acLookFor install` + + LOGN "looking 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 + + # see if we can strip binaries + cat > ngc$$.c << EOF +#include +int main(void) { puts("hello, sailor!"); } +EOF + + if $AC_CC -o ngc$$ ngc$$.c; then + if $PROG_INSTALL -s -m 444 ngc$$ inst$$; then + _strip="-s" + else + unset _strip + LOG "(install -s does not appear to work?)" + fi + rm -f inst$$ + fi + rm -f ngc$$ ngc$$.c + + AC_SUB 'INSTALL' "$PROG_INSTALL" + AC_SUB 'INSTALL_PROGRAM' "$PROG_INSTALL $_strip -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 > ngc$$.c << EOF +#define FOO BAR + +FOO +EOF + + good= + use_cflags= + if $1 $2 $AC_CFLAGS ngc$$.c > ngc$$.o; then + good=1 + use_cflags=1 + elif $1 $2 ngc$$.c > ngc$$.o; then + good=1 + fi + + if [ "$good" ]; then + if grep -v '#define' ngc$$.o | grep -s BAR >/dev/null; then + echo "CPP=[$1], CPP_PIPE=[$2${use_cflags:+ $AC_CFLAGS}]" + AC_SUB 'CPP' "$1" + AC_CPP_FILTER="$1 $2${use_cflags:+ $AC_CFLAGS}" + rm ngc$$.c ngc$$.o + return 0 + fi + fi + rm ngc$$.c 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 () { + test "$AC_CPP_FILTER" && return + + AC_PROG_CC + + 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 acCheckCPP "$AC_CC" -E; then + TLOG " (using \$CC -E as a cpp pipeline)" + return 0 + fi + + 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_config_sed; a C program to do escaping for AC_SUB +__ac_config_sed() { + + + test -x config.sed && return + + echo "generating config.sed" + + AC_PROG_CC + +cat > ngc$$.c << \EOF +#include + +int +main(int argc, char **argv) +{ + char *p; + + if (argc != 3) + return 1; + + printf("s;@%s@;", argv[1]); + + for (p=argv[2]; *p; ++p) { + if ( *p == ';' ) + putchar('\\'); + putchar(*p); + } + + puts(";g"); + return 0; +} +EOF + + if $AC_CC -o config.sed ngc$$.c; then + rm -f ngc$$.c + __config_files="$__config_files config.sed" + else + rm -f ngc$$.c + AC_FAIL "Cannot generate config.sed helper program" + fi +} + +# +# AC_SUB writes a substitution into config.sub +AC_SUB() { + + _target="$1" + shift + + echo "target=$_target, rest=$*" + + __ac_config_sed + ./config.sed "$_target" "$*" >> "$__cwd"/config.sub +} +# +# AC_TEXT writes arbitrary text into config.h +AC_TEXT() { + echo "$@" >> "$__cwd"/config.h +} + +# +# 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 +} + + +AC_TR=`acLookFor tr` +if [ "$AC_TR" ]; then + # try posix-style tr + ABC=`echo abc | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` + if [ "$ABC" = "ABC" ]; then + AC_UPPERCASE="$AC_TR abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ" + AC_UPPER_PAT="ABCDEFGHIJKLMNOPQRSTUVWXYZ" + else + ABC=`echo abc | tr a-z A-Z` + if [ "$ABC" = "ABC" ]; then + AC_UPPERCASE="$AC_TR a-z A-Z" + AC_UPPER_PAT="A-Z" + else + ABC=`echo abc | tr '[a-z]' '[A-Z]'` + if [ "$ABC" = "ABC" ]; then + AC_UPPERCASE="$AC_TR '[a-z]' '[A-Z]'" + AC_UPPER_PAT="'[A-Z]'" + else + AC_FAIL "$AC_TR cannot translate lowercase to uppercase" + return 0 + fi + fi + fi +else + AC_FAIL "configure requires a functional version of tr" +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 | $AC_UPPERCASE | 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 | $AC_UPPERCASE | 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 | $AC_UPPERCASE | 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 | $AC_UPPERCASE | 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 + diff --git a/configure.sh b/configure.sh new file mode 100755 index 0000000..2f2c1c8 --- /dev/null +++ b/configure.sh @@ -0,0 +1,296 @@ +#! /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 +# +ac_help='--enable-amalloc Enable memory allocation debugging +--with-tabstops=N Set tabstops to N characters (default is 4) +--shared Build shared libraries (default is static) +--container Build inside a container +--pkg-config Install pkg-config(1) glue files +--cxx-binding Install header files with c++ wrappers +--github-checkbox[=input] Enable github-style checkboxes in lists + (if =input, use , otherwise + use html ballot entities)' + +LOCAL_AC_OPTIONS=' +set=`locals $*`; +if [ "$set" ]; then + eval $set + shift 1 +else + ac_error=T; +fi' + +locals() { + K=`echo $1 | $AC_UPPERCASE` + case "$K" in + --SHARED) + echo TRY_SHARED=T + ;; + --ENABLE-*) enable=`echo $K | sed -e 's/--ENABLE-//' | tr '-' '_'` + echo WITH_${enable}=T ;; + --DEBIAN-GLITCH) + echo DEBIAN_GLITCH=T + ;; + --CONTAINER) + echo CONTAINER=T + ;; + --H1-TITLE) + echo H1TITLE=T + ;; + --PKG-CONFIG) + echo PKGCONFIG=T + ;; + --CXX-BINDING) + echo CXX_BINDING=T + ;; + --GITHUB-CHECKBOX=ENTITY) + echo GITHUB_CHECKBOX_STYLE=entity + ;; + --GITHUB-CHECKBOX=INPUT) + echo GITHUB_CHECKBOX_STYLE=input + ;; + esac +} + +VERSION=`cat VERSION` +TARGET=markdown +. ./configure.inc + +# if there's a makefile here, it's likely that it's a discount +# makefile and there's bits of an old configuration here. So +# blow everything away before we start the configuration. + +test -f Makefile && make spotless 2>/dev/null >/dev/null + +AC_INIT $TARGET +AC_SUB 'PACKAGE_NAME' lib$TARGET +AC_SUB 'PACKAGE_VERSION' $VERSION + +# define definition list type defaults (for theme) +# +case "`echo "$WITH_DL" | $AC_UPPERCASE`" in + DISCOUNT) AC_DEFINE THEME_DL_MODE 1 ;; + EXTRA) AC_DEFINE THEME_DL_MODE 2 ;; + BOTH) AC_DEFINE THEME_DL_MODE 3 ;; +esac +test "$WITH_FENCED_CODE" && AC_DEFINE THEME_FENCED_CODE 1 + +AC_DEFINE THEME_CF "$THEME_CF" + + +test "$DEBIAN_GLITCH" && AC_DEFINE 'DEBIAN_GLITCH' 1 + +AC_PROG_CC +AC_QUIET AC_PROG git && AC_DEFINE 'HAS_GIT' '1' + +test "$TRY_SHARED" && AC_COMPILER_PIC && AC_CC_SHLIBS + +if [ "IS_BROKEN_CC" ]; then + case "$AC_CC $AC_CFLAGS" in + *-pedantic*) ;; + *) # hack around deficiencies in gcc and clang + # + AC_DEFINE 'while(x)' 'while( (x) != 0 )' + AC_DEFINE 'if(x)' 'if( (x) != 0 )' + + if [ "$IS_CLANG" ]; then + AC_CC="$AC_CC -Wno-implicit-int" + elif [ "$IS_GCC" ]; then + AC_CC="$AC_CC -Wno-return-type -Wno-implicit-int" + fi ;; + esac +fi + +AC_PROG ar || AC_FAIL "$TARGET requires ar" +AC_PROG ranlib + +# should we create a .pc for pkg-config & GNU automake +# +if [ "$PKGCONFIG" ]; then + AC_SUB MK_PKGCONFIG '' +elif AC_PROG pkg-config || AC_PROG automake ; then + PKGCONFIG=true + AC_SUB MK_PKGCONFIG '' +else + AC_SUB MK_PKGCONFIG '#' +fi + +AC_C_VOLATILE +AC_C_CONST +AC_C_INLINE +AC_SCALAR_TYPES sub hdr +AC_CHECK_BASENAME +AC_CHECK_ALLOCA + +AC_CHECK_HEADERS sys/types.h pwd.h && AC_CHECK_FUNCS getpwuid +if AC_CHECK_HEADERS sys/stat.h && AC_CHECK_FUNCS stat; then + +# need to check some of the S_ISxxx stat macros, because they may not +# exist (for notspecial.c) + +cat > ngc$$.c << EOF +#include + +int main(int argc, char **argv) +{ + struct stat info; + + if ( stat(argv[0], &info) != 0 ) + return 1; + + return MACRO(info.st_mode); +} +EOF + LOGN "special file macros in sys/stat.h:" + _none="none" + for x in ISSOCK ISCHR ISFIFO; do + if $AC_CC -DMACRO=S_$x -o ngc$$.o ngc$$.c; then + LOGN " S_${x}" + AC_DEFINE "HAS_${x}" '1' + unset _none + fi + done + LOG "${_none}." + __remove ngc$$.o ngc$$.c +fi + +# find out if the isspace() function on this system is one +# that dumps core of characters with the 8th bit set + +cat > ngc$$.c << EOF +#include +#include + +main() +{ + char text[] = { -3 }; + + return isspace(text[0]); +} +EOF + + if $AC_CC $AC_CFLAGS -o ngc$$ ngc$$.c; then + LOGN "is isspace() broken: " + if ./ngc$$ ; then + LOG "no" + else + AC_CC="$AC_CC -funsigned-char" + + $AC_CC $AC_CFLAGS -o ngc$$ ngc$$.c + + if ./ngc$$; then + LOG "yes (patchable)" + else + LOG "yes (not patchable)" + fi + fi + else + LOG "can't compile test program?" + fi + rm -rf ngc$$* + + +if AC_CHECK_FUNCS srandom; then + AC_DEFINE 'INITRNG(x)' 'srandom((unsigned int)x)' +elif AC_CHECK_FUNCS srand; then + AC_DEFINE 'INITRNG(x)' 'srand((unsigned int)x)' +else + AC_DEFINE 'INITRNG(x)' '(void)1' +fi + +AC_CHECK_FUNCS 'memset((char*)0,0,0)' 'string.h' || \ + AC_CHECK_FUNCS 'memset((char*)0,0,0)' || \ + AC_FAIL "$TARGET requires memset" + +if AC_CHECK_FUNCS random; then + AC_DEFINE 'COINTOSS()' '(random()&1)' +elif AC_CHECK_FUNCS rand; then + AC_DEFINE 'COINTOSS()' '(rand()&1)' +else + AC_DEFINE 'COINTOSS()' '1' +fi + +if AC_CHECK_FUNCS strcasecmp; then + : +elif AC_CHECK_FUNCS stricmp; then + AC_DEFINE strcasecmp stricmp +else + AC_FAIL "$TARGET requires either strcasecmp() or stricmp()" +fi + +if AC_CHECK_FUNCS strncasecmp; then + : +elif AC_CHECK_FUNCS strnicmp; then + AC_DEFINE strncasecmp strnicmp +else + AC_FAIL "$TARGET requires either strncasecmp() or strnicmp()" +fi + +if AC_CHECK_FUNCS fchdir || AC_CHECK_FUNCS getcwd ; then + AC_SUB 'THEME' '' +else + AC_SUB 'THEME' '#' +fi + +if [ -z "$WITH_TABSTOPS" ]; then + TABSTOP=4 +elif [ "$WITH_TABSTOPS" -eq 1 ]; then + TABSTOP=8 +else + TABSTOP=$WITH_TABSTOPS +fi +AC_DEFINE 'TABSTOP' $TABSTOP +AC_SUB 'TABSTOP' $TABSTOP + + +if [ "$WITH_AMALLOC" ]; then + AC_DEFINE 'USE_AMALLOC' 1 + AC_SUB 'AMALLOC' 'amalloc.o' +else + AC_SUB 'AMALLOC' '' +fi + +if [ "$H1TITLE" ]; then + AC_SUB 'H1TITLE' h1title.o + AC_DEFINE USE_H1TITLE 1 +else + AC_SUB 'H1TITLE' '' +fi + +if [ "$GITHUB_CHECKBOX_STYLE" = "entity" ]; then + AC_DEFINE 'CHECKBOX_AS_INPUT' '0' +else + AC_DEFINE 'CHECKBOX_AS_INPUT' '1' +fi + + +[ "$OS_FREEBSD" -o "$OS_DRAGONFLY" ] || AC_CHECK_HEADERS malloc.h + +[ "$WITH_PANDOC_HEADER" ] && AC_DEFINE 'PANDOC_HEADER' '1' + +GENERATE="Makefile version.c mkdio.h" + +if [ "$PKGCONFIG" ]; then + GENERATE="$GENERATE libmarkdown.pc" +fi + +AC_OUTPUT $GENERATE + +if [ "$CXX_BINDING" ]; then + LOG "applying c++ glue to mkdio.h" + mv mkdio.h mkdio.h$$ + ( echo '#ifdef __cplusplus' + echo 'extern "C" {' + echo '#endif' + cat mkdio.h$$ + echo '#ifdef __cplusplus' + echo '}' + echo '#endif' ) > mkdio.h + rm mkdio.h$$ +fi diff --git a/css.c b/css.c new file mode 100644 index 0000000..ecbd445 --- /dev/null +++ b/css.c @@ -0,0 +1,85 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + * + * Copyright (C) 2009 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" + + +/* + * dump out stylesheet sections. + */ +static void +stylesheets(Paragraph *p, Cstring *f) +{ + Line* q; + + for ( ; p ; p = p->next ) { + if ( p->typ == STYLE ) { + for ( q = p->text; q ; q = q->next ) { + Cswrite(f, T(q->text), S(q->text)); + Csputc('\n', f); + } + } + if ( p->down ) + stylesheets(p->down, f); + } +} + + +/* dump any embedded styles to a string + */ +int +mkd_css(Document *d, char **res) +{ + Cstring f; + int size; + + if ( res && d && d->compiled ) { + *res = 0; + CREATE(f); + RESERVE(f, 100); + stylesheets(d->code, &f); + + if ( (size = S(f)) > 0 ) { + /* null-terminate, then strdup() into a free()able memory + * chunk + */ + COMPLETE(f); + *res = strdup(T(f)); + } + DELETE(f); + return size; + } + return EOF; +} + + +/* dump any embedded styles to a file + */ +int +mkd_generatecss(Document *d, FILE *f) +{ + char *res; + int written; + int size = mkd_css(d, &res); + + written = (size > 0) ? fwrite(res,1,size,f) : 0; + + if ( res ) + free(res); + + return (written == size) ? size : EOF; +} diff --git a/cstring.h b/cstring.h new file mode 100644 index 0000000..f3ca6ab --- /dev/null +++ b/cstring.h @@ -0,0 +1,78 @@ +/* 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 +#include + +#ifndef __WITHOUT_AMALLOC +# include "amalloc.h" +#endif + +/* expandable Pascal-style string. + */ +#define STRING(type) struct { type *text; int size, alloc; } + +#define CREATE(x) ( (T(x) = (void*)0), (S(x) = (x).alloc = 0) ) +#define EXPAND(x) (S(x)++)[(S(x) < (x).alloc) \ + ? (T(x)) \ + : (T(x) = T(x) ? realloc(T(x), sizeof T(x)[0] * ((x).alloc += 100)) \ + : malloc(sizeof T(x)[0] * ((x).alloc += 100)) )] + +#define DELETE(x) ALLOCATED(x) ? (free(T(x)), S(x) = (x).alloc = 0) \ + : ( S(x) = 0 ) +#define CLIP(t,i,sz) \ + S(t) -= ( ((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])), \ + (sz)) : 0 + +#define RESERVE(x, sz) T(x) = ((x).alloc > S(x) + (sz) \ + ? T(x) \ + : T(x) \ + ? realloc(T(x), sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))) \ + : malloc(sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x)))) +#define SUFFIX(t,p,sz) \ + memcpy(((S(t) += (sz)) - (sz)) + \ + (T(t) = T(t) ? realloc(T(t), sizeof T(t)[0] * ((t).alloc += sz)) \ + : malloc(sizeof T(t)[0] * ((t).alloc += sz))), \ + (p), sizeof(T(t)[0])*(sz)) + +#define PREFIX(t,p,sz) \ + RESERVE( (t), (sz) ); \ + if ( S(t) ) { memmove(T(t)+(sz), T(t), S(t)); } \ + memcpy( T(t), (p), (sz) ); \ + S(t) += (sz) + +/* reference-style links (and images) are stored in an array + */ +#define T(x) (x).text +#define S(x) (x).size +#define ALLOCATED(x) (x).alloc + +/* 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 E(t) ((t).end) + +#define ATTACH(t, p) ( T(t) ? ( (E(t)->next = (p)), (E(t) = (p)) ) \ + : ( (T(t) = E(t) = (p)) ) ) + +typedef STRING(char) Cstring; + +#define COMPLETE(t) (EXPAND(t) = 0),(S(t)--) + +extern void Csputc(int, Cstring *); +extern int Csprintf(Cstring *, char *, ...); +extern int Cswrite(Cstring *, char *, int); + +#endif/*_CSTRING_D*/ diff --git a/docheader.c b/docheader.c new file mode 100644 index 0000000..c88da19 --- /dev/null +++ b/docheader.c @@ -0,0 +1,54 @@ +/* + * docheader -- get values from the document header + * + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include "config.h" +#include +#include +#include + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" + +static char * +onlyifset(Line *l) +{ + char *ret; + + if ( l->dle < 0 || l->dle >= S(l->text) ) + return 0; + + ret = T(l->text) + l->dle; + + return ret[0] ? ret : 0; +} + +char * +mkd_doc_title(Document *doc) +{ + if ( doc && doc->title ) + return onlyifset(doc->title); + return 0; +} + + +char * +mkd_doc_author(Document *doc) +{ + if ( doc && doc->author ) + return onlyifset(doc->author); + return 0; +} + + +char * +mkd_doc_date(Document *doc) +{ + if ( doc && doc->date ) + return onlyifset(doc->date); + return 0; +} diff --git a/dumptree.c b/dumptree.c new file mode 100644 index 0000000..2791221 --- /dev/null +++ b/dumptree.c @@ -0,0 +1,163 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + * + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include +#include "markdown.h" +#include "cstring.h" +#include "amalloc.h" + +struct frame { + int indent; + char c; +}; + +typedef STRING(struct frame) Stack; + +static char * +Pptype(int typ) +{ + switch (typ) { + case WHITESPACE: return "whitespace"; + case CODE : return "code"; + case QUOTE : return "quote"; + case MARKUP : return "markup"; + case HTML : return "html"; + case DL : return "dl"; + case UL : return "ul"; + case OL : return "ol"; + case LISTITEM : return "item"; + case HDR : return "header"; + case HR : return "hr"; + case TABLE : return "table"; + case SOURCE : return "source"; + case STYLE : return "style"; + default : return "mystery node!"; + } +} + +static void +pushpfx(int indent, char c, Stack *sp) +{ + struct frame *q = &EXPAND(*sp); + + q->indent = indent; + q->c = c; +} + + +static void +poppfx(Stack *sp) +{ + S(*sp)--; +} + + +static void +changepfx(Stack *sp, char c) +{ + char ch; + + if ( S(*sp) > 0 ) { + ch = T(*sp)[S(*sp)-1].c; + + if ( ch == '+' || ch == '|' ) + T(*sp)[S(*sp)-1].c = c; + } +} + + +static void +printpfx(Stack *sp, FILE *f) +{ + int i; + char c; + + if ( S(*sp) > 0 ) { + c = T(*sp)[S(*sp)-1].c; + + if ( c == '+' || c == '-' ) { + fprintf(f, "--%c", c); + T(*sp)[S(*sp)-1].c = (c == '-') ? ' ' : '|'; + } + else + for ( i=0; i < S(*sp); i++ ) { + if ( i ) + fprintf(f, " "); + fprintf(f, "%*s%c", T(*sp)[i].indent + 2, " ", T(*sp)[i].c); + if ( T(*sp)[i].c == '`' ) + T(*sp)[i].c = ' '; + } + fprintf(f, "--"); + } +} + + +static void +dumptree(Paragraph *pp, Stack *sp, FILE *f) +{ + int count; + Line *p; + int d; + static char *Begin[] = { 0, "P", "center" }; + + while ( pp ) { + if ( !pp->next ) + changepfx(sp, '`'); + printpfx(sp, f); + + if ( pp->typ == HDR ) + d += fprintf(f, "[h%d", pp->hnumber); + else + d = fprintf(f, "[%s", Pptype(pp->typ)); + if ( pp->ident ) + d += fprintf(f, " %s", pp->ident); + + if ( pp->para_flags ) + d += fprintf(f, " %x", pp->para_flags); + + if ( pp->align > 1 ) + d += fprintf(f, ", <%s>", Begin[pp->align]); + + for (count=0, p=pp->text; p; ++count, (p = p->next) ) + ; + + if ( count ) + d += fprintf(f, ", %d line%s", count, (count==1)?"":"s"); + +#if EXTENDED_DEBUG + if ( pp->text && T(pp->text->text) ) + d += fprintf(f, " <%.*s>", S(pp->text->text), T(pp->text->text)); +#endif + + d += fprintf(f, "]"); + + if ( pp->down ) { + pushpfx(d, pp->down->next ? '+' : '-', sp); + dumptree(pp->down, sp, f); + poppfx(sp); + } + else fputc('\n', f); + pp = pp->next; + } +} + + +int +mkd_dump(Document *doc, FILE *out, mkd_flag_t *flags, char *title) +{ + Stack stack; + + if ( mkd_compile(doc, flags) && doc->code ) { + + CREATE(stack); + pushpfx(fprintf(out, "%s", title), doc->code->next ? '+' : '-', &stack); + dumptree(doc->code, &stack, out); + DELETE(stack); + + return 0; + } + return -1; +} diff --git a/emmatch.c b/emmatch.c new file mode 100644 index 0000000..8e1b480 --- /dev/null +++ b/emmatch.c @@ -0,0 +1,189 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + * + * Copyright (C) 2010 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" + + +/* emmatch: the emphasis mangler that's run after a block + * of html has been generated. + * + * It should create MarkdownTest_1.0 (and _1.0.3) + * compatible emphasis for non-pathological cases + * and it should fail in a standards-compliant way + * when someone attempts to feed it junk. + * + * Emmatching is done after the input has been + * processed into a STRING (f->Q) of text and + * emphasis blocks. After ___mkd_emblock() finishes, + * it truncates f->Q and leaves the rendered paragraph + * if f->out. + */ + + +/* empair() -- find the NEAREST matching emphasis token (or + * subtoken of a 3+ long emphasis token. + */ +static int +empair(MMIOT *f, int first, int last, int match) +{ + + int i; + block *begin, *p; + + begin = &T(f->Q)[first]; + + for (i=first+1; i <= last; i++) { + p = &T(f->Q)[i]; + + if ( (p->b_type != bTEXT) && (p->b_count <= 0) ) + continue; /* break? */ + + if ( p->b_type == begin->b_type ) { + if ( p->b_count == match ) /* exact match */ + return i; + + if ( p->b_count > 2 ) /* fuzzy match */ + return i; + } + } + return 0; +} /* empair */ + + +/* emfill() -- if an emphasis token has leftover stars or underscores, + * convert them back into character and append them to b_text. + */ +static void +emfill(block *p) +{ + int j; + + if ( p->b_type == bTEXT ) + return; + + for (j=0; j < p->b_count; j++) + EXPAND(p->b_text) = p->b_char; + p->b_count = 0; +} /* emfill */ + + +static void +emclose(MMIOT *f, int first, int last) +{ + int j; + + for (j=first+1; jQ)[j]); +} + + +static struct emtags { + char open[10]; + char close[10]; + int size; +} emtags[] = { { "" , "", 5 }, { "", "", 9 } }; + + +static void emblock(MMIOT*,int,int); + + +/* emmatch() -- match emphasis for a single emphasis token. + */ +static void +emmatch(MMIOT *f, int first, int last) +{ + block *start = &T(f->Q)[first]; + int e, e2, match; + + switch (start->b_count) { + case 2: if ( e = empair(f,first,last,match=2) ) + break; + case 1: e = empair(f,first,last,match=1); + break; + case 0: return; + default: + e = empair(f,first,last,1); + e2= empair(f,first,last,2); + + if ( e2 >= e ) { + e = e2; + match = 2; + } + else + match = 1; + break; + } + + if ( e ) { + /* if we found emphasis to match, match it, recursively call + * emblock to match emphasis inside the new html block, add + * the emphasis markers for the block, then (tail) recursively + * call ourself to match any remaining emphasis on this token. + */ + block *end = &T(f->Q)[e]; + + end->b_count -= match; + start->b_count -= match; + + emblock(f, first, e); + + PREFIX(start->b_text, emtags[match-1].open, emtags[match-1].size-1); + SUFFIX(end->b_post, emtags[match-1].close, emtags[match-1].size); + + emmatch(f, first, last); + } +} /* emmatch */ + + +/* emblock() -- walk a blocklist, attempting to match emphasis + */ +static void +emblock(MMIOT *f, int first, int last) +{ + int i; + + for ( i = first; i <= last; i++ ) + if ( T(f->Q)[i].b_type != bTEXT ) + emmatch(f, i, last); + emclose(f, first, last); +} /* emblock */ + + +/* ___mkd_emblock() -- emblock a string of blocks, then concatenate the + * resulting text onto f->out. + */ +void +___mkd_emblock(MMIOT *f) +{ + int i; + block *p; + + if ( S(f->Q) > 0 ) { + emblock(f, 0, S(f->Q)-1); + + for (i=0; i < S(f->Q); i++) { + p = &T(f->Q)[i]; + emfill(p); + + if ( S(p->b_post) ) { SUFFIX(f->out, T(p->b_post), S(p->b_post)); + DELETE(p->b_post); } + if ( S(p->b_text) ) { SUFFIX(f->out, T(p->b_text), S(p->b_text)); + DELETE(p->b_text); } + } + S(f->Q) = 0; + } +} /* ___mkd_emblock */ diff --git a/flagprocs.c b/flagprocs.c new file mode 100644 index 0000000..5d7230a --- /dev/null +++ b/flagprocs.c @@ -0,0 +1,50 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + * + * Copyright (C) 2007-2011 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "markdown.h" +#include "amalloc.h" + +#if HAVE_LIBGEN_H +#include +#endif + +void +mkd_set_flag_num(mkd_flag_t *p, unsigned long bit) +{ + if ( p && (bit < MKD_NR_FLAGS) ) + set_mkd_flag(p, bit); +} + + +void +mkd_clr_flag_num(mkd_flag_t *p, unsigned long bit) +{ + if ( p && (bit < MKD_NR_FLAGS) ) + clear_mkd_flag(p, bit); +} + + +void +mkd_set_flag_bitmap(mkd_flag_t *p, long bits) +{ + int i; + + if ( p == 0 ) + return; + + for (i=0; i < 8*sizeof(long) && i < MKD_NR_FLAGS; i++) + if ( bits & (1< +#include "markdown.h" + +struct flagnames { + int flag; + char *name; +}; + +static struct flagnames flagnames[] = { + { MKD_NOLINKS, "!LINKS" }, + { MKD_NOIMAGE, "!IMAGE" }, + { MKD_NOPANTS, "!PANTS" }, + { MKD_NOHTML, "!HTML" }, + { MKD_TAGTEXT, "TAGTEXT" }, + { MKD_NO_EXT, "!EXT" }, + { MKD_CDATA, "CDATA" }, + { MKD_NOSUPERSCRIPT, "!SUPERSCRIPT" }, + { MKD_STRICT, "STRICT" }, + { MKD_NOTABLES, "!TABLES" }, + { MKD_NOSTRIKETHROUGH,"!STRIKETHROUGH" }, + { MKD_TOC, "TOC" }, + { MKD_1_COMPAT, "MKD_1_COMPAT" }, + { MKD_AUTOLINK, "AUTOLINK" }, + { MKD_SAFELINK, "SAFELINK" }, + { MKD_NOHEADER, "!HEADER" }, + { MKD_TABSTOP, "TABSTOP" }, + { MKD_NODIVQUOTE, "!DIVQUOTE" }, + { MKD_NOALPHALIST, "!ALPHALIST" }, + { MKD_EXTRA_FOOTNOTE, "FOOTNOTE" }, + { MKD_NOSTYLE, "!STYLE" }, + { MKD_DLDISCOUNT, "DLDISCOUNT" }, + { MKD_DLEXTRA, "DLEXTRA" }, + { MKD_FENCEDCODE, "FENCEDCODE" }, + { MKD_IDANCHOR, "IDANCHOR" }, + { MKD_GITHUBTAGS, "GITHUBTAGS" }, + { MKD_NORMAL_LISTITEM, "NORMAL_LISTITEM" }, + { MKD_URLENCODEDANCHOR, "URLENCODEDANCHOR" }, + { MKD_LATEX, "LATEX" }, + { MKD_EXPLICITLIST, "EXPLICITLIST" }, + { MKD_ALT_AS_TITLE, "ALT_AS_TITLE" }, + { MKD_EXTENDED_ATTR, "EXTENDED_ATTR" }, + { MKD_HTML5, "HTML5" }, +}; +#define NR(x) (sizeof x/sizeof x[0]) + + +int +mkd_flag_isset(mkd_flag_t *flags, int i) +{ + return flags ? is_flag_set(flags, i) : 0; +} + + +void +mkd_flags_are(FILE *f, mkd_flag_t* flags, int htmlplease) +{ + int i; + int not, set, even=1; + char *name; + + if ( htmlplease ) + fprintf(f, "\n"); + for (i=0; i < NR(flagnames); i++) { + set = mkd_flag_isset(flags, flagnames[i].flag); + name = flagnames[i].name; + if ( not = (*name == '!') ) { + ++name; + set = !set; + } + + if ( htmlplease ) { + if ( even ) fprintf(f, " "); + fprintf(f, ""); + if ( !even ) fprintf(f, "\n"); + } + even = !even; + } + if ( htmlplease ) { + if ( even ) fprintf(f, "\n"); + fprintf(f, "
"); + } + else + fputc(' ', f); + + if ( !set ) + fprintf(f, htmlplease ? "" : "!"); + + fprintf(f, "%s", name); + + if ( htmlplease ) { + if ( !set ) + fprintf(f, ""); + fprintf(f, "
\n"); + } +} + +void +mkd_mmiot_flags(FILE *f, MMIOT *m, int htmlplease) +{ + if ( m ) + mkd_flags_are(f, &(m->flags), htmlplease); +} + +void +mkd_init_flags(mkd_flag_t *p) +{ + memset(p, 0, sizeof(*p)); +} + +mkd_flag_t * +mkd_flags(void) +{ + mkd_flag_t *p = calloc( 1, sizeof(mkd_flag_t) ); + + if ( p ) + mkd_init_flags(p); + + return p; +} + + +mkd_flag_t * +mkd_copy_flags(mkd_flag_t *original) +{ + mkd_flag_t *copy = mkd_flags(); + + if ( original && copy ) + *copy = *original; + + return copy; +} + +void +mkd_free_flags(mkd_flag_t *rip) +{ + if (rip) free(rip); +} diff --git a/generate.c b/generate.c new file mode 100644 index 0000000..10c3d9f --- /dev/null +++ b/generate.c @@ -0,0 +1,2164 @@ +/* markdown: a C implementation of John Gruber's Markdown markup language. + * + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" +#include "tags.h" + +typedef int (*stfu)(const void*,const void*); +typedef void (*spanhandler)(MMIOT*,int); + +/* forward declarations */ +static void text(MMIOT *f); +static Paragraph *display(Paragraph*, MMIOT*); + +/* externals from markdown.c */ +int __mkd_footsort(Footnote *, Footnote *); + +/* + * push text into the generator input buffer + */ +static void +push(char *bfr, int size, MMIOT *f) +{ + while ( size-- > 0 ) + EXPAND(f->in) = *bfr++; +} + + +/* + * push a character into the generator input buffer + */ +static void +pushc(char c, MMIOT *f) +{ + EXPAND(f->in) = c; +} + + +/* look characters ahead of the cursor. + */ +static inline int +peek(MMIOT *f, int i) +{ + + i += (f->isp-1); + + return (i >= 0) && (i < S(f->in)) ? (unsigned char)T(f->in)[i] : EOF; +} + + +/* pull a byte from the input buffer + */ +static inline unsigned int +pull(MMIOT *f) +{ + return ( f->isp < S(f->in) ) ? (unsigned char)T(f->in)[f->isp++] : EOF; +} + + +/* return a pointer to the current position in the input buffer. + */ +static inline char* +cursor(MMIOT *f) +{ + return T(f->in) + f->isp; +} + + +static inline int +isthisspace(MMIOT *f, int i) +{ + int c = peek(f, i); + + if ( c == EOF ) + return 1; + if ( c & 0x80 ) + return 0; + return isspace(c) || (c < ' '); +} + + +static inline int +isthisalnum(MMIOT *f, int i) +{ + int c = peek(f, i); + + return (c != EOF) && isalnum(c); +} + + +static inline int +isthisnonword(MMIOT *f, int i) +{ + return isthisspace(f, i) || ispunct(peek(f,i)); +} + + +/* return/set the current cursor position + * (when setting the current cursor position we also need to flush the + * last character written cache) + */ +#define mmiotseek(f,x) ((f->isp = x), (f->last = 0)) +#define mmiottell(f) (f->isp) + + +/* move n characters forward ( or -n characters backward) in the input buffer. + */ +static void +shift(MMIOT *f, int i) +{ + if (f->isp + i >= 0 ) + f->isp += i; +} + + +/* Qchar() + */ +static void +Qchar(int c, MMIOT *f) +{ + block *cur; + + if ( S(f->Q) > 0 ) + cur = &T(f->Q)[S(f->Q)-1]; + else { + cur = &EXPAND(f->Q); + memset(cur, 0, sizeof *cur); + cur->b_type = bTEXT; + } + + EXPAND(cur->b_text) = c; + +} + + +/* Qstring() + */ +static void +Qstring(char *s, MMIOT *f) +{ + while (*s) + Qchar(*s++, f); +} + + +/* Qwrite() + */ +static void +Qwrite(char *s, int size, MMIOT *f) +{ + while (size-- > 0) + Qchar(*s++, f); +} + + +/* Qprintf() + */ +static void +Qprintf(MMIOT *f, char *fmt, ...) +{ + char bfr[80]; + va_list ptr; + + va_start(ptr,fmt); + vsnprintf(bfr, sizeof bfr, fmt, ptr); + va_end(ptr); + Qstring(bfr, f); +} + + +/* Qanchor() prints out a suitable-for-id-tag version of a string + */ +static void +Qanchor(char *name, MMIOT *f) +{ + mkd_string_to_anchor(name, strlen(name), (mkd_sta_function_t)Qchar, f, 1, f); +} + + +/* Qem() + */ +static void +Qem(MMIOT *f, char c, int count) +{ + block *p = &EXPAND(f->Q); + + memset(p, 0, sizeof *p); + p->b_type = (c == '*') ? bSTAR : bUNDER; + p->b_char = c; + p->b_count = count; + + memset(&EXPAND(f->Q), 0, sizeof(block)); +} + + +/* generate html from a markup fragment + */ +void +___mkd_reparse(char *bfr, int size, mkd_flag_t* flags, MMIOT *f, char *esc) +{ + MMIOT sub; + struct escaped e; + + ___mkd_initmmiot(&sub, f->footnotes, flags); + + ___mkd_or_flags(&sub.flags, &f->flags); + + + + sub.cb = f->cb; + sub.ref_prefix = f->ref_prefix; + + if ( esc ) { + sub.esc = &e; + e.up = f->esc; + e.text = esc; + } + else + sub.esc = f->esc; + + push(bfr, size, &sub); + pushc(0, &sub); + S(sub.in)--; + + text(&sub); + ___mkd_emblock(&sub); + + Qwrite(T(sub.out), S(sub.out), f); + /* inherit the last character printed from the reparsed + * text; this way superscripts can work when they're + * applied to something embedded in a link + */ + f->last = sub.last; + + ___mkd_freemmiot(&sub, f->footnotes); +} + + +/* + * check the escape list for special cases + */ +static int +escaped(MMIOT *f, char c) +{ + struct escaped *thing = f->esc; + + while ( thing ) { + if ( strchr(thing->text, c) ) + return 1; + thing = thing->up; + } + return 0; +} + + +/* + * write out a url, escaping problematic characters + */ +static void +puturl(char *s, int size, MMIOT *f, int display) +{ + unsigned char c; + + if ( size && s[0] == '<' && s[size-1] == '>' ) { + /* urls encased in <> need to have the <>'s removed */ + s++; + size -= 2; + } + + while ( size-- > 0 ) { + c = *s++; + + if ( c == '\\' && size-- > 0 ) { + c = *s++; + + if ( !( ispunct(c) || isspace(c) ) ) + Qchar('\\', f); + } + + if ( c == '&' ) + Qstring("&", f); + else if ( c == '<' ) + Qstring("<", f); + else if ( c == '"' ) + Qstring("%22", f); + else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) ) + Qchar(c, f); + else if ( c == MKD_EOLN ) /* untokenize hard return */ + Qstring(" ", f); + else + Qprintf(f, "%%%02X", c); + } +} + + +/* advance forward until the next character is not whitespace + */ +static int +eatspace(MMIOT *f) +{ + int c; + + for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) ) + ; + return c; +} + + +/* (match (a (nested (parenthetical (string.))))) + */ +static int +parenthetical(int in, int out, MMIOT *f) +{ + int size, indent, c; + + for ( indent=1,size=0; indent; size++ ) { + if ( (c = pull(f)) == EOF ) + return EOF; + else if ( (c == '\\') && (peek(f,1) == out || peek(f,1) == in) ) { + ++size; + pull(f); + } + else if ( c == in ) + ++indent; + else if ( c == out ) + --indent; + } + return size ? (size-1) : 0; +} + + +/* extract a []-delimited label from the input stream. + */ +static int +linkylabel(MMIOT *f, Cstring *res) +{ + char *ptr = cursor(f); + int size; + + if ( (size = parenthetical('[',']',f)) != EOF ) { + T(*res) = ptr; + S(*res) = size; + return 1; + } + return 0; +} + + +/* see if the quote-prefixed linky segment is actually a title. + */ +static int +linkytitle(MMIOT *f, char quote, Footnote *ref) +{ + int whence = mmiottell(f); + char *title = cursor(f); + char *e; + register int c; + + while ( (c = pull(f)) != EOF ) { + e = cursor(f); + if ( c == quote ) { + if ( (c = eatspace(f)) == ')' ) { + T(ref->title) = 1+title; + S(ref->title) = (e-title)-2; + return 1; + } + } + } + mmiotseek(f, whence); + return 0; +} + + +/* extract a =WxH size from the input stream + */ +static int +linkysize(MMIOT *f, Footnote *ref) +{ + Cstring height, width; + int whence = mmiottell(f); + int c; + + CREATE(height); + CREATE(width); + + if ( isspace(peek(f,0)) ) { + pull(f); /* eat '=' */ + + c = peek(f,1); + + if ( isdigit(c) ) { + for ( c = pull(f); isdigit(c); c = pull(f)) + EXPAND(width) = c; + if ( c == '%' ) { + EXPAND(width) = c; + c = pull(f); + } + COMPLETE(width); + } + else + pull(f); + + if ( c == 'x' ) { + + c = pull(f); + if ( isdigit(c) ) { + while (isdigit(c) ) { + EXPAND(height) = c; + c = pull(f); + } + if ( c == '%' ) { + EXPAND(height) = c; + c = pull(f); + } + COMPLETE(height); + } + + if ( isspace(c) ) + c = eatspace(f); + + if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) { + ref->height = height; + ref->width = width; + return 1; + } + } + DELETE(height); + DELETE(width); + } + mmiotseek(f, whence); + return 0; +} + + +/* extract a <...>-encased url from the input stream. + * (markdown 1.0.2b8 compatibility; older versions + * of markdown treated the < and > as syntactic + * sugar that didn't have to be there. 1.0.2b8 + * requires a closing >, and then falls into the + * title or closing ) + */ +static int +linkybroket(MMIOT *f, int image, Footnote *p) +{ + int c; + int good = 0; + + T(p->link) = cursor(f); + for ( S(p->link)=0; (c = pull(f)) != '>'; ++S(p->link) ) { + /* pull in all input until a '>' is found, or die trying. + */ + if ( c == EOF ) + return 0; + else if ( (c == '\\') && ispunct(peek(f,2)) ) { + ++S(p->link); + pull(f); + } + } + + c = eatspace(f); + + /* next nonspace needs to be a title, a size, or ) + */ + if ( ( c == '\'' || c == '"' ) && linkytitle(f,c,p) ) + good=1; + else if ( image && (c == '=') && linkysize(f,p) ) + good=1; + else + good=( c == ')' ); + + if ( good ) { + if ( peek(f, 1) == ')' ) + pull(f); + + ___mkd_tidy(&p->link); + } + + return good; +} /* linkybroket */ + + +/* extract a {}-delimited extended attribute from the input + * stream. + */ +static void +linky_extended_attributes(MMIOT *f, struct footnote *p, int start) +{ + int c; + + mmiotseek(f, start+1); + + while ( (c = pull(f)) != '}' ) + EXPAND(p->extended_attr) = c; + COMPLETE(p->extended_attr); +} + + +/* extract a (-prefixed url from the input stream. + * the label is either of the format ``, where I + * extract until I find a >, or it is of the format + * `text`, where I extract until I reach a ')', a quote, + * or (if image) a '=' + */ +static int +linkyurl(MMIOT *f, int image, Footnote *p) +{ + int c; + int mayneedtotrim=0; + + if ( (c = eatspace(f)) == EOF ) + return 0; + + if ( c == '<' ) { + pull(f); + if ( !is_flag_set(&f->flags, MKD_1_COMPAT) ) + return linkybroket(f,image,p); + mayneedtotrim=1; + } + + T(p->link) = cursor(f); + for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) { + if ( c == EOF ) + return 0; + else if ( (c == '"' || c == '\'') && linkytitle(f, c, p) ) + break; + else if ( image && (c == '=') && linkysize(f, p) ) + break; + else if ( (c == '\\') && ispunct(peek(f,2)) ) { + ++S(p->link); + pull(f); + } + pull(f); + } + if ( peek(f, 1) == ')' ) + pull(f); + + /* possible extended attributes? */ + if ( is_flag_set(&f->flags, MKD_EXTENDED_ATTR) && (peek(f,1) == '{') ) { + int loc = mmiottell(f); + int size; + + pull(f); + + if ( (size = parenthetical('{','}',f)) != EOF ) + linky_extended_attributes(f, p, loc); + else + mmiotseek(f, loc); + } + + ___mkd_tidy(&p->link); + + if ( mayneedtotrim && (S(p->link) > 0) && (T(p->link)[S(p->link)-1] == '>') ) + --S(p->link); + + return 1; +} + + + +/* prefixes for + */ +static struct _protocol { + char *name; + int nlen; +} protocol[] = { +#define _aprotocol(x) { x, (sizeof x)-1 } + _aprotocol( "https:" ), + _aprotocol( "http:" ), + _aprotocol( "news:" ), + _aprotocol( "ftp:" ), +#undef _aprotocol +}; +#define NRPROTOCOLS (sizeof protocol / sizeof protocol[0]) + + +static int +isautoprefix(char *text, int size) +{ + int i; + struct _protocol *p; + + for (i=0, p=protocol; i < NRPROTOCOLS; i++, p++) + if ( (size >= p->nlen) && strncasecmp(text, p->name, p->nlen) == 0 ) + return 1; + return 0; +} + + +/* + * all the tag types that linkylinky can produce are + * defined by this structure. + */ +typedef struct linkytype { + char *pat; + int szpat; + char *link_pfx; /* tag prefix and link pointer (eg: "" */ + char *text_sfx; /* text suffix (eg: "" */ + mkd_flag_t flags; /* reparse flags */ + int kind; /* tag is url or something else? */ +#define IS_URL 0x01 +} linkytype; + +static linkytype imaget = { 0, 0, "\"",", { { [MKD_NOIMAGE] = 1, + [MKD_TAGTEXT] = 1, + [MKD_ALT_AS_TITLE] = 1 } }, IS_URL }; +static linkytype linkt = { 0, 0, "", "", { {[MKD_NOLINKS] = 1} }, IS_URL }; + +/* + * pseudo-protocols for [][]; + * + * id: generates tag + * class: generates tag + * raw: just dump the link without any processing + */ +static linkytype specials[] = { + { "id:", 3, "", "", {0}, 0 }, + { "raw:", 4, 0, 0, 0, 0, 0, { { [MKD_NOHTML] = 1 } }, 0 }, + { "lang:", 5, "", "", {0}, 0 }, + { "abbr:", 5, "", "", {0}, 0 }, + { "class:", 6, "", "", {0}, 0 }, +} ; + +#define NR(x) (sizeof x / sizeof x[0]) + +/* see if t contains one of our pseudo-protocols. + */ +static linkytype * +pseudo(Cstring t) +{ + int i; + linkytype *r; + + for ( i=0, r=specials; i < NR(specials); i++,r++ ) { + if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) ) + return r; + } + return 0; +} + + +/* print out the start of an `img' or `a' tag, applying callbacks as needed. + */ +static void +printlinkyref(MMIOT *f, linkytype *tag, char *link, int size) +{ + char *edit; + + if ( is_flag_set(&f->flags, IS_LABEL) ) + return; + + Qstring(tag->link_pfx, f); + + if ( tag->kind & IS_URL ) { + if ( f->cb && f->cb->e_url.func && (edit = (*f->cb->e_url.func)(link, size, f->cb->e_url.data)) ) { + puturl(edit, strlen(edit), f, 0); + if ( f->cb->e_url.free ) + (*f->cb->e_url.free)(edit, strlen(edit), f); + } + else + puturl(link + tag->szpat, size - tag->szpat, f, 0); + } + else { + mkd_flag_t tagtext; + mkd_init_flags(&tagtext); + set_mkd_flag(&tagtext, MKD_TAGTEXT); + ___mkd_reparse(link + tag->szpat, size - tag->szpat, &tagtext, f, 0); + } + + Qstring(tag->link_sfx, f); + + if ( f->cb && f->cb->e_flags.func && (edit = (*f->cb->e_flags.func)(link, size, f->cb->e_flags.data)) ) { + Qchar(' ', f); + Qstring(edit, f); + if ( f->cb->e_flags.free ) + (*f->cb->e_flags.free) (edit, strlen(edit), f); + } +} /* printlinkyref */ + + +/* helper function for php markdown extra footnotes; allow the user to + * define a prefix tag instead of just `fn` + */ +static char * +p_or_nothing(MMIOT *p) +{ + return p->ref_prefix ? p->ref_prefix : "fn"; +} + + +/* php markdown extra/daring fireball style print footnotes + */ +static int +extra_linky(MMIOT *f, Cstring text, Footnote *ref) +{ + if ( ref->fn_flags & REFERENCED ) + return 0; + + if ( is_flag_set(&f->flags, IS_LABEL) ) + ___mkd_reparse(T(text), S(text), &(linkt.flags), f, 0); + else { + ref->fn_flags |= REFERENCED; + ref->refnumber = ++ f->footnotes->reference; + Qprintf(f, "%d", + p_or_nothing(f), ref->refnumber, + p_or_nothing(f), ref->refnumber, ref->refnumber); + } + return 1; +} /* extra_linky */ + + + +/* check a url (or fragment) to see that it begins with a known good + * protocol (or no protocol at all) + */ +static int +safelink(Cstring link) +{ + char *p, *colon; + + if ( T(link) == 0 ) /* no link; safe */ + return 1; + + p = T(link); + if ( (colon = memchr(p, ':', S(link))) == 0 ) + return 1; /* no protocol specified: safe */ + + if ( !isalpha(*p) ) /* protocol/method is [alpha][alnum or '+.-'] */ + return 1; + while ( ++p < colon ) + if ( !(isalnum(*p) || *p == '.' || *p == '+' || *p == '-') ) + return 1; + + return isautoprefix(T(link), S(link)); +} + + +/* print out a linky (or fail if it's Not Allowed) + */ +static int +linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref) +{ + linkytype *tag; + static mkd_flag_t tagtext = { {[MKD_TAGTEXT] = 1} }; + + if ( image ) + tag = &imaget; + else if ( tag = pseudo(ref->link) ) { + if ( is_flag_set(&f->flags, MKD_NO_EXT) || is_flag_set(&f->flags, MKD_STRICT) + || is_flag_set(&f->flags, MKD_SAFELINK) ) + return 0; + } + else if ( is_flag_set(&f->flags, MKD_SAFELINK) && !is_flag_set(&f->flags, MKD_STRICT) + && !safelink(ref->link) ) + /* if MKD_SAFELINK, only accept links that are local or + * a well-known protocol + */ + return 0; + else + tag = &linkt; + + if ( ANY_FLAGS(&f->flags, &tag->flags) ) + return 0; + + if ( is_flag_set(&f->flags, IS_LABEL) ) + ___mkd_reparse(T(text), S(text), &(tag->flags), f, 0); + else if ( tag->link_pfx ) { + printlinkyref(f, tag, T(ref->link), S(ref->link)); + + if ( tag->WxH ) { + if ( S(ref->height) > 0 ) Qprintf(f," height=\"%s\"", T(ref->height)); + if ( S(ref->width) > 0 ) Qprintf(f, " width=\"%s\"", T(ref->width)); + } + if ( S(ref->extended_attr) > 0 ) + Qprintf(f, " %s", T(ref->extended_attr)); + + if ( S(ref->title) || (is_flag_set(&f->flags, MKD_ALT_AS_TITLE) && is_flag_set(&tag->flags, MKD_ALT_AS_TITLE)) ) { + Qstring(" title=\"", f); + if ( S(ref->title) ) + ___mkd_reparse(T(ref->title), S(ref->title), &tagtext, f, 0); + else + ___mkd_reparse(T(text), S(text), &tagtext, f, 0); + Qchar('"', f); + } + + Qstring(tag->text_pfx, f); + ___mkd_reparse(T(text), S(text), &(tag->flags), f, 0); + Qstring(tag->text_sfx, f); + } + else + Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f); + + return 1; +} /* linkyformat */ + + +/* + * process embedded links and images + */ +static int +linkylinky(int image, MMIOT *f) +{ + int start = mmiottell(f); + Cstring name; + Footnote key, *ref; + + int status = 0; + int extra_footnote = 0; + + CREATE(name); + memset(&key, 0, sizeof key); + + if ( linkylabel(f, &name) ) { + if ( peek(f,1) == '(' ) { + pull(f); + if ( linkyurl(f, image, &key) ) + status = linkyformat(f, name, image, &key); + } + else { + int goodlink, implicit_mark = mmiottell(f); + + if ( is_flag_set(&f->flags, MKD_EXTRA_FOOTNOTE) + && !is_flag_set(&f->flags, MKD_STRICT) + && (!image) + && S(name) + && T(name)[0] == '^' ) { + extra_footnote = 1; + goodlink = 1; + } + else { + if ( isspace(peek(f,1)) ) + pull(f); + + if ( peek(f,1) == '[' ) { + pull(f); /* consume leading '[' */ + goodlink = linkylabel(f, &key.tag); + } + else { + /* new markdown implicit name syntax doesn't + * require a second [] + */ + mmiotseek(f, implicit_mark); + goodlink = !is_flag_set(&f->flags, MKD_1_COMPAT); + } + } + + if ( goodlink ) { + if ( !S(key.tag) ) { + DELETE(key.tag); + T(key.tag) = T(name); + S(key.tag) = S(name); + } + + if ( ref = bsearch(&key, T(f->footnotes->note), + S(f->footnotes->note), + sizeof key, (stfu)__mkd_footsort) ) { + if ( extra_footnote ) + status = extra_linky(f,name,ref); + else + status = linkyformat(f, name, image, ref); + } + } + } + } + + DELETE(name); + ___mkd_freefootnote(&key); + + if ( status == 0 ) + mmiotseek(f, start); + + return status; +} + + +/* write a character to output, doing text escapes ( & -> &, + * > -> > < -> < ) + */ +static void +cputc(int c, MMIOT *f) +{ + switch (c) { + case '&': Qstring("&", f); break; + case '>': Qstring(">", f); break; + case '<': Qstring("<", f); break; + default : Qchar(c, f); break; + } +} + + +/* + * convert an email address to a string of nonsense + */ +static void +mangle(char *s, int len, MMIOT *f) +{ + while ( len-- > 0 ) { +#if DEBIAN_GLITCH + Qprintf(f, "&#%02d;", *((unsigned char*)(s++)) ); +#else + Qstring("&#", f); + Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) ); +#endif + } +} + + +/* nrticks() -- count up a row of tick marks + */ +static int +nrticks(int offset, int tickchar, MMIOT *f) +{ + int tick = 0; + + while ( peek(f, offset+tick) == tickchar ) tick++; + + return tick; +} /* nrticks */ + + +/* matchticks() -- match a certain # of ticks, and if that fails + * match the largest subset of those ticks. + * + * if a subset was matched, return the # of ticks + * that were matched. + */ +static int +matchticks(MMIOT *f, int tickchar, int ticks, int *endticks) +{ + int size, count, c; + int subsize=0, subtick=0; + + *endticks = ticks; + for (size = 0; (c=peek(f,size+ticks)) != EOF; size ++) { + if ( (c == tickchar) && ( count = nrticks(size+ticks,tickchar,f)) ) { + if ( count == ticks ) + return size; + else if ( count ) { + if ( (count > subtick) && (count < ticks) ) { + subsize = size; + subtick = count; + } + size += count; + } + } + } + if ( subsize ) { + *endticks = subtick; + return subsize; + } + return 0; +} /* matchticks */ + + +/* code() -- write a string out as code. The only characters that have + * special meaning in a code block are * `<' and `&' , which + * are /always/ expanded to < and & + */ +static void +code(MMIOT *f, char *s, int length) +{ + int i,c; + + for ( i=0; i < length; i++ ) + if ( (c = s[i]) == MKD_EOLN) /* expand back to 2 spaces */ + Qstring(" ", f); + else if ( c == '\\' && (i < length-1) && escaped(f, s[i+1]) ) + cputc(s[++i], f); + else + cputc(c, f); +} /* code */ + + +/* delspan() -- write out a chunk of text, blocking with ... + */ +static void +delspan(MMIOT *f, int size) +{ + Qstring("", f); + ___mkd_reparse(cursor(f)-1, size, NULL, f, 0); + Qstring("", f); +} + + +/* + * LaTeXspan() -- write out a chunk of text as a section of (unmangled) + * input for a LaTeX preprocessor + */ +static void +LaTeXspan(MMIOT *f, int size) +{ + Qchar('$', f); + if ( size > 0 ) + code(f, cursor(f)-1, size); + Qchar('$', f); +} + + +/* codespan() -- write out a chunk of text as code, trimming one + * space off the front and/or back as appropriate. + */ +static void +codespan(MMIOT *f, int size) +{ + int i=0; + + if ( size > 1 && peek(f, size-1) == ' ' ) --size; + if ( peek(f,i) == ' ' ) ++i, --size; + + Qstring("", f); + code(f, cursor(f)+(i-1), size); + Qstring("", f); +} /* codespan */ + + +/* before letting a tag through, validate against + * MKD_NOLINKS and MKD_NOIMAGE + */ +static int +forbidden_tag(MMIOT *f) +{ + int c = toupper(peek(f, 1)); + + if ( is_flag_set(&f->flags, MKD_NOHTML) ) + return 1; + + if ( c == 'A' && is_flag_set(&f->flags, MKD_NOLINKS) && !isthisalnum(f,2) ) + return 1; + if ( c == 'I' && is_flag_set(&f->flags, MKD_NOIMAGE) + && strncasecmp(cursor(f)+1, "MG", 2) == 0 + && !isthisalnum(f,4) ) + return 1; + return 0; +} + + +/* Check a string to see if it looks like a mail address + * "looks like a mail address" means alphanumeric + some + * specials, then a `@`, then alphanumeric + some specials, + * but with a `.` + */ +static int +maybe_address(char *p, int size) +{ + int ok = 0; + char *q = p; + + for ( ;size && (isalnum(*q) || strchr("._-+*", *q)); ++q, --size) + ; + + if ( ! (size && q > p && *q == '@') ) + return 0; + + --size, ++q; + + if ( size && *q == '.' ) return 0; + + for ( ;size && (isalnum(*q) || strchr("._-+", *q)); ++q, --size ) + if ( *q == '.' && size > 1 ) ok = 1; + + return size ? 0 : ok; +} + + +/* The size-length token at cursor(f) is either a mailto:, an + * implicit mailto:, one of the approved url protocols, or just + * plain old text. If it's a mailto: or an approved protocol, + * linkify it, otherwise say "no" + */ +static int +process_possible_link(MMIOT *f, int size) +{ + int address= 0; + int mailto = 0; + char *text = cursor(f); + + if ( is_flag_set(&f->flags, MKD_NOLINKS) ) return 0; + + if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) { + /* if it says it's a mailto, it's a mailto -- who am + * I to second-guess the user? + */ + address = 1; + mailto = 7; /* 7 is the length of "mailto:"; we need this */ + } + else + address = maybe_address(text, size); + + if ( address ) { + Qstring("", f); + mangle(text+mailto, size-mailto, f); + Qstring("", f); + return 1; + } + else if ( isautoprefix(text, size) ) { + printlinkyref(f, &linkt, text, size); + Qchar('>', f); + puturl(text,size,f, 1); + Qstring("", f); + return 1; + } + return 0; +} /* process_possible_link */ + + +/* + * check if a character is one of the things the reference implementation considers valid for starting + * a html(ish) tag + */ +static inline int +is_a_strict_tag_prefix(int c) +{ + return isalpha(c) || (c == '/') || (c == '!') || (c == '$') || (c == '?'); +} + + +/* a < may be just a regular character, the start of an embedded html + * tag, or the start of an . If it's an automatic + * link, we also need to know if it's an email address because if it + * is we need to mangle it in our futile attempt to cut down on the + * spaminess of the rendered page. + */ +static int +maybe_tag_or_link(MMIOT *f) +{ + int c, size=0; + + if ( is_flag_set(&f->flags, MKD_TAGTEXT) ) + return 0; + + c = peek(f, 1); + + if ( is_a_strict_tag_prefix(c) ) { + /* By decree of Markdown.pl *this is a tag* and we want to absorb everything up + * to the next '>', unless interrupted by another '<' OR a '`', at which point + * we kick it back to the caller as plain old text. + */ + size=1; + while ( (c=peek(f,size+1)) != '>' ) { + if ( c == EOF || c == '<' ) + return 0; + if ( is_flag_set(&f->flags, MKD_STRICT) ) { + if ( c == '`' ) + return 0; + } + size++; + } + } + + if ( size > 0 ) { + if ( process_possible_link(f, size) ) { + shift(f, size+1); + return 1; + } + else { + int i; + + if ( forbidden_tag(f) ) + return 0; + + for ( i=0; i <= size+1; i++ ) { + c = peek(f,i); + + if ( (c == '&') && (i > 0) ) + Qstring("&", f); + else + Qchar(c, f); + } + + shift(f, size+1); + return 1; + } + } + + return 0; +} + + +/* autolinking means that all inline html is . A + * autolink url is alphanumerics, slashes, periods, underscores, + * the at sign, colon, and the % character. + */ +static int +maybe_autolink(MMIOT *f) +{ + register int c; + int size; + + /* greedily scan forward for the end of a legitimate link. + */ + for ( size=0; (c=peek(f, size+1)) != EOF; size++ ) { + if ( c == '\\' ) { + if ( peek(f, size+2) != EOF ) + ++size; + } + else if ( c & 0x80 ) /* HACK: ignore utf-8 extended characters */ + continue; + else if ( isspace(c) || strchr("'\"()[]{}<>`", c) || c == MKD_EOLN ) + break; + } + + if ( (size > 1) && process_possible_link(f, size) ) { + shift(f, size); + return 1; + } + return 0; +} + + +/* smartyquote code that's common for single and double quotes + */ +static int +smartyquote(int *flags, char typeofquote, MMIOT *f) +{ + int bit = (typeofquote == 's') ? 0x01 : 0x02; + + if ( bit & (*flags) ) { + if ( isthisnonword(f,1) ) { + Qprintf(f, "&r%cquo;", typeofquote); + (*flags) &= ~bit; + return 1; + } + } + else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) { + Qprintf(f, "&l%cquo;", typeofquote); + (*flags) |= bit; + return 1; + } + return 0; +} + + +static int +islike(MMIOT *f, char *s) +{ + int len; + int i; + + if ( s[0] == '|' ) { + if ( !isthisnonword(f, -1) ) + return 0; + ++s; + } + + if ( !(len = strlen(s)) ) + return 0; + + if ( s[len-1] == '|' ) { + if ( !isthisnonword(f,len-1) ) + return 0; + len--; + } + + for (i=1; i < len; i++) + if (tolower(peek(f,i)) != s[i]) + return 0; + return 1; +} + + +static struct smarties { + char c0; + char *pat; + char *entity; + int shift; +} smarties[] = { + { '\'', "'s|", "rsquo", 0 }, + { '\'', "'t|", "rsquo", 0 }, + { '\'', "'re|", "rsquo", 0 }, + { '\'', "'ll|", "rsquo", 0 }, + { '\'', "'ve|", "rsquo", 0 }, + { '\'', "'m|", "rsquo", 0 }, + { '\'', "'d|", "rsquo", 0 }, + { '-', "---", "mdash", 2 }, + { '-', "--", "ndash", 1 }, + { '.', "...", "hellip", 2 }, + { '.', ". . .", "hellip", 4 }, + { '(', "(c)", "copy", 2 }, + { '(', "(r)", "reg", 2 }, + { '(', "(tm)", "trade", 3 }, + { '3', "|3/4|", "frac34", 2 }, + { '3', "|3/4ths|", "frac34", 2 }, + { '1', "|1/2|", "frac12", 2 }, + { '1', "|1/4|", "frac14", 2 }, + { '1', "|1/4th|", "frac14", 2 }, + { '&', "�", 0, 3 }, +} ; +#define NRSMART ( sizeof smarties / sizeof smarties[0] ) + + +/* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm) + */ +static int +smartypants(int c, int *flags, MMIOT *f) +{ + int i; + + if ( is_flag_set(&f->flags, MKD_NOPANTS) + || is_flag_set(&f->flags, MKD_TAGTEXT) + || is_flag_set(&f->flags, IS_LABEL) ) + return 0; + + for ( i=0; i < NRSMART; i++) + if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) { + if ( smarties[i].entity ) + Qprintf(f, "&%s;", smarties[i].entity); + shift(f, smarties[i].shift); + return 1; + } + + switch (c) { + case '<' : return 0; + case '\'': if ( smartyquote(flags, 's', f) ) return 1; + break; + + case '"': if ( smartyquote(flags, 'd', f) ) return 1; + break; + + case '`': if ( peek(f, 1) == '`' ) { + int j = 2; + + while ( (c=peek(f,j)) != EOF ) { + if ( c == '\\' ) + j += 2; + else if ( c == '`' ) + break; + else if ( c == '\'' && peek(f, j+1) == '\'' ) { + Qstring("“", f); + ___mkd_reparse(cursor(f)+1, j-2, NULL, f, 0); + Qstring("”", f); + shift(f,j+1); + return 1; + } + else ++j; + } + + } + break; + } + return 0; +} /* smartypants */ + + +/* process latex with arbitrary 2-character ( $$ .. $$, \[ .. \], \( .. \) + * delimiters + */ +static int +mathhandler(MMIOT *f, int e1, int e2) +{ + int i = 0; + + while(peek(f, ++i) != EOF) { + if (peek(f, i) == e1 && peek(f, i+1) == e2) { + cputc(peek(f,-1), f); + cputc(peek(f, 0), f); + while ( i-- > -1 ) + cputc(pull(f), f); + return 1; + } + } + return 0; +} + + +/* process a body of text encased in some sort of tick marks. If it + * works, generate the output and return 1, otherwise just return 0 and + * let the caller figure it out. + */ +static int +tickhandler(MMIOT *f, int tickchar, int minticks, int allow_space, spanhandler spanner) +{ + int endticks, size; + int tick = nrticks(0, tickchar, f); + + if ( !allow_space && isspace(peek(f,tick)) ) + return 0; + + if ( (tick >= minticks) && (size = matchticks(f,tickchar,tick,&endticks)) ) { + if ( endticks < tick ) { + size += (tick - endticks); + tick = endticks; + } + + if ( size > 0 ) { + shift(f, tick); + (*spanner)(f,size); + shift(f, size+tick-1); + return 1; + } + } + return 0; +} + +#define tag_text(f) is_flag_set(&((f)->flags), MKD_TAGTEXT) + + +static void +text(MMIOT *f) +{ + int c, j; + int rep; + int smartyflags = 0; + + + while (1) { + if ( is_flag_set(&f->flags, MKD_AUTOLINK) && !is_flag_set(&f->flags, MKD_STRICT) + && isalpha(peek(f,1)) + && !tag_text(f) ) + maybe_autolink(f); + + c = pull(f); + + if (c == EOF) + break; + + if ( smartypants(c, &smartyflags, f) ) + continue; + switch (c) { + case 0: break; + + case MKD_EOLN: + Qstring(tag_text(f) ? " " : "
", f); + break; + + case '>': if ( tag_text(f) ) + Qstring(">", f); + else + Qchar(c, f); + break; + + case '"': if ( tag_text(f) ) + Qstring(""", f); + else + Qchar(c, f); + break; + + case '!': if ( peek(f,1) == '[' ) { + pull(f); + if ( tag_text(f) || !linkylinky(1, f) ) + Qstring("![", f); + } + else + Qchar(c, f); + break; + + case '[': if ( tag_text(f) || !linkylinky(0, f) ) + Qchar(c, f); + break; + /* A^B -> AB */ + case '^': if ( is_flag_set(&f->flags, MKD_NOSUPERSCRIPT) + || is_flag_set(&f->flags, MKD_STRICT) + || is_flag_set(&f->flags, MKD_TAGTEXT) + || (f->last == 0) + || ((ispunct(f->last) || isspace(f->last)) + && f->last != ')') + || isthisspace(f,1) ) + Qchar(c,f); + else { + char *sup = cursor(f); + int len = 0; + + if ( peek(f,1) == '(' ) { + int here = mmiottell(f); + pull(f); + + if ( (len = parenthetical('(',')',f)) == EOF ) { + mmiotseek(f,here); + Qchar(c, f); + break; + } + sup++; + } + else { + while ( isthisalnum(f,1+len) ) + ++len; + if ( !len ) { + Qchar(c,f); + break; + } + shift(f,len); + } + Qstring("",f); + ___mkd_reparse(sup, len, NULL, f, "()"); + Qstring("", f); + } + break; + case '_': + /* Underscores don't count if they're in the middle of a word */ + if ( !is_flag_set(&f->flags, MKD_STRICT) + && isthisalnum(f,-1) && isthisalnum(f,1) ) { + Qchar(c, f); + break; + } + case '*': + /* Underscores & stars don't count if they're out in the middle + * of whitespace */ + if ( isthisspace(f,-1) && isthisspace(f,1) ) { + Qchar(c, f); + break; + } + /* else fall into the regular old emphasis case */ + if ( tag_text(f) ) + Qchar(c, f); + else { + for (rep = 1; peek(f,1) == c; pull(f) ) + ++rep; + Qem(f,c,rep); + } + break; + +#ifdef TYPORA +#define ticktick(f,c) (tickhandler(f,c,2,0,delspan) || tickhandler(f,c,1,0,subspan)) +#else +#define ticktick(f,c) tickhandler(f,c,2,0,delspan) +#endif + + case '~': if ( is_flag_set(&f->flags, MKD_NOSTRIKETHROUGH) + || is_flag_set(&f->flags, MKD_STRICT) + || is_flag_set(&f->flags, MKD_TAGTEXT) + || !ticktick(f,c) ) + Qchar(c, f); + break; + + case '`': if ( tag_text(f) || !tickhandler(f,c,1,1,codespan) ) + Qchar(c, f); + break; + + case '\\': switch ( c = pull(f) ) { + case '&': Qstring("&", f); + break; + case '<': c = peek(f,1); + if ( (c == EOF) || isspace(c) ) + Qstring("<", f); + else { + /* Markdown.pl does not escape <[nonwhite] + * sequences */ + Qchar('\\', f); + shift(f, -1); + } + + break; + case '^': if ( is_flag_set(&f->flags, MKD_NOSUPERSCRIPT) ) { + Qchar('\\', f); + shift(f,-1); + break; + } + Qchar(c, f); + break; + + case ':': case '|': + if ( is_flag_set(&f->flags, MKD_NOTABLES) || is_flag_set(&f->flags, MKD_STRICT) ) { + Qchar('\\', f); + shift(f,-1); + break; + } + Qchar(c, f); + break; + + case EOF: Qchar('\\', f); + break; + + case '[': + case '(': if ( is_flag_set(&f->flags, MKD_LATEX) + && !is_flag_set(&f->flags, MKD_STRICT) + && mathhandler(f, '\\', (c =='(')?')':']') ) + break; + /* else fall through to default */ + + default: if ( escaped(f,c) || + strchr(">#.-+{}]![*_\\()`", c) ) + Qchar(c, f); + else { + Qchar('\\', f); + shift(f, -1); + } + break; + } + break; + + case '<': if ( !maybe_tag_or_link(f) ) { + if ( is_flag_set(&f->flags, MKD_STRICT) && is_a_strict_tag_prefix(peek(f,1)) ) + Qchar(c, f); + else + Qstring("<", f); + } + break; + + case '&': j = (peek(f,1) == '#' ) ? 2 : 1; + while ( isthisalnum(f,j) ) + ++j; + + if ( peek(f,j) != ';' ) + Qstring("&", f); + else + Qchar(c, f); + break; + + case '$': if ( is_flag_set(&f->flags, MKD_LATEX) && !is_flag_set(&f->flags, MKD_STRICT) ) { + if ( peek(f,1) == '$' ) { + pull(f); + if ( mathhandler(f, '$', '$') ) { + break; + } + shift(f,-1); /* push back the second '$' */ + } + else if ( tickhandler(f,c,1,1,LaTeXspan) ) + break; + } + /* fall through to default: */ + + default: f->last = c; + Qchar(c, f); + break; + } + } + /* truncate the input string after we've finished processing it */ + S(f->in) = f->isp = 0; +} /* text */ + + +/* print a header block + */ +static void +printheader(Paragraph *pp, MMIOT *f) +{ + if ( is_flag_set(&f->flags, MKD_IDANCHOR) ) { + Qprintf(f, "hnumber); + if ( pp->label && is_flag_set(&f->flags, MKD_TOC) && !is_flag_set(&f->flags, MKD_STRICT) ) { + Qstring(" id=\"", f); + Qanchor(pp->label, f); + Qchar('"', f); + } + Qchar('>', f); + } else { + if ( pp->label && is_flag_set(&f->flags, MKD_TOC) && !is_flag_set(&f->flags, MKD_STRICT) ) { + Qstring("
label, f); + Qstring("\">\n", f); + } + Qprintf(f, "", pp->hnumber); + } + push(T(pp->text->text), S(pp->text->text), f); + text(f); + Qprintf(f, "", pp->hnumber); +} + + +enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT }; + +static char* alignments[] = { "", " style=\"text-align:center;\"", + " style=\"text-align:left;\"", + " style=\"text-align:right;\"" }; + +typedef STRING(int) Istring; + +static int +splat(Line *p, char *block, Istring align, int force, MMIOT *f) +{ + int first, + idx = p->dle, + colno = 0; + + + ___mkd_tidy(&p->text); + if ( S(p->text) > 0 && (T(p->text)[S(p->text)-1] == '|') ) + --S(p->text); + + Qstring("\n", f); + while ( idx < S(p->text) ) { + first = idx; + if ( force && (colno >= S(align)-1) ) + idx = S(p->text); + else + while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') ) { + if ( T(p->text)[idx] == '\\' ) + ++idx; + ++idx; + } + + Qprintf(f, "<%s%s>", + block, + alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]); + ___mkd_reparse(T(p->text)+first, idx-first, NULL, f, "|"); + Qprintf(f, "\n", block); + idx++; + colno++; + } + if ( force ) + while (colno < S(align) ) { + Qprintf(f, "<%s>\n", block, block); + ++colno; + } + Qstring("\n", f); + return colno; +} + + +static int +printtable(Paragraph *pp, MMIOT *f) +{ + /* header, dashes, then lines of content */ + + Line *hdr, *dash, *body; + Istring align; + int hcols,start; + char *p; + enum e_alignments it; + + hdr = pp->text; + dash= hdr->next; + body= dash->next; + + if ( T(hdr->text)[hdr->dle] == '|' ) { + /* trim leading pipe off all lines + */ + Line *r; + for ( r = pp->text; r; r = r->next ) + r->dle ++; + } + + /* figure out cell alignments */ + + CREATE(align); + + for (p=T(dash->text), start=dash->dle; start < S(dash->text); ) { + char first, last; + int end; + + last=first=0; + for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) { + if ( p[end] == '\\' ) + ++ end; + else if ( !isspace(p[end]) ) { + if ( !first) first = p[end]; + last = p[end]; + } + } + it = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT) + : (( last == ':') ? a_RIGHT : a_NONE ); + + EXPAND(align) = it; + start = 1+end; + } + + Qstring("\n", f); + Qstring("\n", f); + hcols = splat(hdr, "th", align, 0, f); + Qstring("\n", f); + + if ( hcols < S(align) ) + S(align) = hcols; + else + while ( hcols > S(align) ) + EXPAND(align) = a_NONE; + + Qstring("\n", f); + for ( ; body; body = body->next) + splat(body, "td", align, 1, f); + Qstring("\n", f); + Qstring("
\n", f); + + DELETE(align); + return 1; +} + +/* external formatter caller for code blocks + */ +static int +code_callback(Line *t, char *lang, int fenced, Line **ret, MMIOT *f) +{ + if ( f && f->cb->e_codefmt.func ) { + /* external code block formatter; copy the text into a buffer, + * call the formatter to style it, then dump that styled text + * directly to the queue + */ + char *text; + char *fmt; + int size, copy_p; + Line *p; + + for (size=0, p = t; p && (fenced ? p->is_fenced : 1); p = p->next ) + size += 1+S(p->text); + + text = malloc(1+size); + + for ( copy_p = 0; t && (fenced ? t->is_fenced : 1); t = t->next ) { + memcpy(text+copy_p, T(t->text), S(t->text)); + copy_p += S(t->text); + text[copy_p++] = '\n'; + } + text[copy_p] = 0; + + + fmt = (*(f->cb->e_codefmt.func))(text, copy_p, (lang && lang[0]) ? lang : 0); + free(text); + + if ( fmt ) { + Qwrite(fmt, strlen(fmt), f); + *ret = t; + if ( f->cb->e_codefmt.free ) (*f->cb->e_codefmt.free)(fmt, strlen(fmt), f); + return 1; + } + } + /* either the external formatter failed or doesn't exist, + * so fall back to the traditional codeblock format + */ + *ret = 0; + return 0; +} + + + +static Line * +printfenced(Line *t, MMIOT *f) +{ + Line *ret; + + + Qstring("
fence_class )
+	Qprintf(f, " class=\"%s\"", t->fence_class);
+    Qchar('>', f);
+
+    if ( !code_callback(t, t->fence_class, 1, &ret, f) ) {
+	while ( (t = t->next) && t->is_fenced ) {
+	    code(f, T(t->text), S(t->text));
+	    Qchar('\n', f);
+	}
+	ret = t;
+    }
+
+    Qstring("
\n", f); + return ret; +} + + +static int +printblock(Paragraph *pp, MMIOT *f) +{ + static char *Begin[] = { "", "

", "

" }; + static char *End[] = { "", "

","
" }; + Line *t = pp->text; + int align = pp->align; + + Qstring(Begin[align], f); + do { + if ( t->is_fenced ) { + text(f); + t = printfenced(t, f); + } + else if ( S(t->text) ) { + if ( t->next && 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, f); + pushc(MKD_EOLN, f); + pushc('\n', f); + } + else { + ___mkd_tidy(&t->text); + push(T(t->text), S(t->text), f); + if ( t->next ) + pushc('\n', f); + } + } + } while (t && (t = t->next) ); + text(f); + Qstring(End[align], f); + + return 1; +} + + +static void +printcode(Line *t, char *lang, MMIOT *f) +{ + int blanks; + Line *ret; + + + Qstring("
", f);
+
+    if ( !code_callback(t, lang, 0, &ret, f) ) {
+	for ( blanks = 0; t ; t = t->next ) {
+	    if ( S(t->text) > t->dle ) {
+		while ( blanks ) {
+		    Qchar('\n', f);
+		    --blanks;
+		}
+		code(f, T(t->text), S(t->text));
+		Qchar('\n', f);
+	    }
+	    else blanks++;
+	}
+    }
+    Qstring("
", f); +} + + +static void +printhtml(Line *t, MMIOT *f) +{ + int blanks; + + for ( blanks=0; t ; t = t->next ) + if ( S(t->text) ) { + for ( ; blanks; --blanks ) + Qchar('\n', f); + + Qwrite(T(t->text), S(t->text), f); + Qchar('\n', f); + } + else + blanks++; +} + + +static void +htmlify_paragraphs(Paragraph *p, MMIOT *f) +{ + ___mkd_emblock(f); + + while (( p = display(p, f) )) { + ___mkd_emblock(f); + Qstring("\n\n", f); + } +} + + +static void +li_htmlify(Paragraph *p, char *arguments, int flags, MMIOT *f) +{ + ___mkd_emblock(f); + + Qprintf(f, ""); +#if CHECKBOX_AS_INPUT + if ( flags & GITHUB_CHECK ) { + Qprintf(f, ""); + } +#else + if ( flags & GITHUB_CHECK ) + Qprintf(f, flags & IS_CHECKED ? "☑" : "☐"); +#endif + + htmlify_paragraphs(p, f); + + Qprintf(f, ""); + ___mkd_emblock(f); +} + + +static void +htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f) +{ + ___mkd_emblock(f); + if ( block ) + Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments); + + htmlify_paragraphs(p, f); + + if ( block ) + Qprintf(f, "", block); + ___mkd_emblock(f); +} + + +static void +definitionlist(Paragraph *p, MMIOT *f) +{ + Line *tag; + + if ( p ) { + Qstring("
\n", f); + + for ( ; p ; p = p->next) { + for ( tag = p->text; tag; tag = tag->next ) { + Qstring("
", f); + ___mkd_reparse(T(tag->text), S(tag->text), NULL, f, 0); + Qstring("
\n", f); + } + + htmlify(p->down, "dd", p->ident, f); + Qchar('\n', f); + } + + Qstring("
", f); + } +} + + +static void +listdisplay(int typ, Paragraph *p, MMIOT* f) +{ + if ( p ) { + Qprintf(f, "<%cl", (typ==UL)?'u':'o'); + if ( typ == AL ) + Qprintf(f, " type=\"a\""); + Qprintf(f, ">\n"); + + for ( ; p ; p = p->next ) { + li_htmlify(p->down, p->ident, p->para_flags, f); + Qchar('\n', f); + } + + Qprintf(f, "\n", (typ==UL)?'u':'o'); + } +} + + +/* dump out a Paragraph in the desired manner + */ +static Paragraph* +display(Paragraph *p, MMIOT *f) +{ + if ( !p ) return 0; + + switch ( p->typ ) { + case STYLE: + case WHITESPACE: + break; + + case HTML: + printhtml(p->text, f); + break; + + case CODE: + printcode(p->text, p->lang, f); + break; + + case QUOTE: + htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f); + break; + + case UL: + case OL: + case AL: + listdisplay(p->typ, p->down, f); + break; + + case DL: + definitionlist(p->down, f); + break; + + case HR: + Qstring("
", f); + break; + + case HDR: + printheader(p, f); + break; + + case TABLE: + printtable(p, f); + break; + + case SOURCE: + htmlify(p->down, 0, 0, f); + break; + + default: + printblock(p, f); + break; + } + return p->next; +} + + +/* dump out a list of footnotes + */ +static void +mkd_extra_footnotes(MMIOT *m) +{ + int j, i; + Footnote *t; + + if ( m->footnotes->reference == 0 ) + return; + + Csprintf(&m->out, "\n
\n
\n
    \n"); + + for ( i=1; i <= m->footnotes->reference; i++ ) { + for ( j=0; j < S(m->footnotes->note); j++ ) { + t = &T(m->footnotes->note)[j]; + if ( (t->refnumber == i) && (t->fn_flags & REFERENCED) ) { + Csprintf(&m->out, "
  1. \n", + p_or_nothing(m), t->refnumber); + htmlify(t->text, 0, 0, m); + Csprintf(&m->out, "", + p_or_nothing(m), t->refnumber); + Csprintf(&m->out, "
  2. \n"); + } + } + } + Csprintf(&m->out, "
\n
\n"); +} + + +/* return a pointer to the compiled markdown + * document. + */ +int +mkd_document(Document *p, char **res) +{ + int size; + + if ( p && p->compiled ) { + if ( ! p->html ) { + htmlify(p->code, 0, 0, p->ctx); + if ( is_flag_set(&p->ctx->flags, MKD_EXTRA_FOOTNOTE) + && !is_flag_set(&p->ctx->flags, MKD_STRICT) ) + mkd_extra_footnotes(p->ctx); + p->html = 1; + size = S(p->ctx->out); + + if ( (size == 0) || T(p->ctx->out)[size-1] ) { + /* Add a null byte at the end of the generated html, + * but pretend it doesn't exist. + */ + COMPLETE(p->ctx->out); + } + } + + *res = T(p->ctx->out); + return S(p->ctx->out); + } + return EOF; +} diff --git a/gethopt.3 b/gethopt.3 new file mode 100644 index 0000000..b6bcaab --- /dev/null +++ b/gethopt.3 @@ -0,0 +1,195 @@ +.\" Copyright (c) 1988, 1991 Regents of the University of California. +.\" Copyright (c) 2017-2024 Jessica Loren 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. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. +.\" +.Dd Jan 23, 2017 +.Dt GETHOPT 3 +.Os Mastodon +.Sh NAME +.Nm gethopt +.Nd get option letter or word from argv +.Sh SYNOPSIS +.Fd #include "gethopt.h" + +.Bd -literal -compact +struct h_opt { + int option; + char *optword; + char optchar; + int opthasarg; + char *optdesc; +} ; +.Ed +.Ft char* +.Fn hoptarg "struct h_context* argctx" +.Ft int +.Fn hoptind "struct h_context* argctx" +.Ft int +.Fn hopterr "struct h_context* argctx" "int flag" +.Ft char +.Fn hoptopt "struct h_context* argctx" +.Ft void +.Fn hoptset "struct h_context* argctx" "int argc" "char** argv" +.Ft struct h_opt* +.Fn gethopt "struct h_context* argctx" "struct h_opt* optarray" "int nropts" + +.Sh DESCRIPTION +The +.Fn gethopt +function gets +the next +.Em known +option word or character from +.Fa argctx . +An option is +.Em known +if it has been specified in the array of accepted options +.Fa optarray . +.Pp +The option array +.Fa optarray +contains records with either an option word, character, or both, +a flag saying the option needs an argument, a short description +of the option, and an +.Va option +key (which is there for the convenience of the calling program; +.Fn gethopt +does not use it in any way.). +It does not matter to +.Fn getopt +if a following argument has leading white space. +.Pp +On return from +.Fn gethopt , +.Fn hoptarg +returns an option argument, if it is anticipated, +and the variable +.Fn hoptind +contains the index to the next +.Fa argv +argument for a subsequent call +to +.Fn gethopt . +.Pp +.Fn +gethopt +uses a (semi) opaque data blob to hold the current state, which +must be initialized by the +.Fn hoptset +function. +.Pp +The +.Fn gethopt +function +returns +.Dv HOPTERR +if a non-recognized +option is encountered, +and NULL when it reaches the end of the options on the command line.. +.Pp +The interpretation of options in the argument list may be cancelled +by +.Ql - +(single dash) or +.Ql -- +(double dash) which causes +.Fn getopt +to signal the end of argument processing and return an +.Dv NULL . +When all options have been processed (i.e., up to the first non-option +argument), +.Fn gethopt +returns +.Dv NULL . +.Sh DIAGNOSTICS +If the +.Fn gethopt +function encounters a character not found in +.Va optarray +or detects +a missing option argument +it returns +.Dv HOPTERR +(and writes an error message to the +.Em stderr +if +.Fn hopterr +is used to turn error reporting on.) +.Sh EXAMPLE +.Bd -literal -compact +struct h_opt opts[] = { + { 0, "css", 0, 1, "css file" }, + { 1, "header", 0, 1, "header file" }, + { 2, 0, 'a', 0, "option a (no arg)" }, + { 3, 0, 'b', 1, "option B (with arg)" }, + { 4, "help", '?', 0, "help message" }, +} ; + +#define NROPT (sizeof opts/sizeof opts[0]) + + +int +main(int argc, char **argv) +{ + struct h_opt *ret; + struct h_context ctx; + + hoptset(&ctx, argc, argv); + hopterr(&ctx, 1); + + while (( ret = gethopt(&ctx, opts, NROPT) )) { + + if ( ret != HOPTERR ) { + if ( ret->optword ) + printf("%s", ret->optword); + else + printf("%c", ret->optchar); + + if ( ret->opthasarg ) { + if ( hoptarg(&ctx) ) + printf(" = %s", hoptarg(&ctx)); + else + printf(" with no argument?"); + } + puts(ret->optdesc ? ret->optdesc : ""); + } + } + + argc -= hoptind(&ctx); + argv += hoptind(&ctx); + +.Ed +.Sh HISTORY +The +.Fn gethopt +function was a quick hack to replace manually parsing full-word arguments +in +.Va discount . diff --git a/gethopt.c b/gethopt.c new file mode 100644 index 0000000..585e0e8 --- /dev/null +++ b/gethopt.c @@ -0,0 +1,347 @@ +/* + * gehopt; options processing with both single-character and whole-word + * options both introduced with - + */ + +#include +#include + +#include "gethopt.h" + + +void +hoptset(struct h_context *ctx, int argc, char **argv) +{ + memset(ctx, 0, sizeof *ctx); + ctx->argc = argc; + ctx->argv = argv; + ctx->optind = 1; +} + + +char * +hoptarg(struct h_context *ctx) +{ + return ctx->optarg; +} + +int +hoptind(struct h_context *ctx) +{ + return ctx->optind; +} + +char +hoptopt(struct h_context *ctx) +{ + return ctx->optopt; +} + + +int +hopterr(struct h_context *ctx, int val) +{ + int old = ctx->opterr; + + ctx->opterr = !!val; + return old; +} + + +struct h_opt * +gethopt(struct h_context *ctx, struct h_opt *opts, int nropts) +{ + int i; + int dashes; + + + if ( (ctx == 0) || ctx->optend || (ctx->optind >= ctx->argc) ) + return 0; + + ctx->optarg = 0; + ctx->optopt = 0; + + if ( ctx->optchar == 0) { + /* check for leading - + */ + if ( ctx->argv[ctx->optind][0] != '-' ) { + /* out of arguments */ + ctx->optend = 1; + return 0; + } + + if ( ctx->argv[ctx->optind][1] == 0 + || strcmp(ctx->argv[ctx->optind], "--") == 0 ) { + /* option list finishes with - or -- token + */ + ctx->optend = 1; + ctx->optind++; + return 0; + } + + dashes = 1; + if ( ctx->argv[ctx->optind][dashes] == '-' ) { + /* support GNU-style long option double-dash prefix + * (if gethopt is passed an unknown option with a double-dash + * prefix, it won't match a word and then the second dash + * will be scanned as if it was a regular old single-character + * option.) + */ + dashes = 2; + } + + for ( i=0; i < nropts; i++ ) { + if ( ! opts[i].optword ) + continue; + + if (strcmp(opts[i].optword, dashes+(ctx->argv[ctx->optind]) ) == 0 ) { + if ( opts[i].opthasarg ) { + if ( ctx->argc > ctx->optind ) { + ctx->optarg = ctx->argv[ctx->optind+1]; + ctx->optind += 2; + } + else { + /* word argument with required arg at end of + *command line + */ + if ( ctx->opterr ) + fprintf(stderr, + "%s: option requires an argument -- %s\n", + ctx->argv[0], opts[i].optword); + ctx->optind ++; + return HOPTERR; + } + } + else { + ctx->optind ++; + } + return &opts[i]; + } + } + ctx->optchar = 1; + } + + ctx->optopt = ctx->argv[ctx->optind][ctx->optchar++]; + + if ( !ctx->optopt ) { + /* fell off the end of this argument */ + ctx->optind ++; + ctx->optchar = 0; + return gethopt(ctx, opts, nropts); + } + + for ( i=0; ioptopt ) { + /* found a single-char option! + */ + if ( opts[i].opthasarg ) { + if ( ctx->argv[ctx->optind][ctx->optchar] ) { + /* argument immediately follows this options (-Oc) + */ + ctx->optarg = &ctx->argv[ctx->optind][ctx->optchar]; + ctx->optind ++; + ctx->optchar = 0; + } + else if ( ctx->optind < ctx->argc-1 ) { + /* argument is next arg (-O c) + */ + ctx->optarg = &ctx->argv[ctx->optind+1][0]; + ctx->optind += 2; + ctx->optchar = 0; + } + else { + /* end of arg string (-O); set optarg to null, return + * (should it opterr on me?) + */ + ctx->optarg = 0; + ctx->optind ++; + ctx->optchar = 0; + if ( ctx->opterr ) + fprintf(stderr, + "%s: option requires an argument -- %c\n", + ctx->argv[0], opts[i].optchar); + return HOPTERR; + } + } + else { + if ( !ctx->argv[ctx->optind][ctx->optchar] ) { + ctx->optind ++; + ctx->optchar = 0; + } + } + return &opts[i]; + } + } + if ( ctx->opterr ) + fprintf(stderr, "%s: illegal option -- %c\n", ctx->argv[0], ctx->optopt); + return HOPTERR; +} + + +void +hoptdescribe(char *pgm, struct h_opt opts[], int nropts, char *arguments, int verbose) +{ + int i; + int sz; + + if ( verbose ) { + fprintf(stderr, "usage: %s", pgm); + if ( nropts > 0 ) + fprintf(stderr, " [options]"); + if ( arguments ) + fprintf(stderr, " %s", arguments); + fputc('\n', stderr); + } + else + fprintf(stderr, "usage: %s", pgm); + + /* print out the options that don't have flags first */ + + if ( verbose ) { + int maxoptwidth = 0; + int maxargwidth = 0; + int hasoptchar = 0; + int j; + + if ( nropts > 0 ) + fprintf(stderr, "options:\n"); + + /* find column widths */ + for ( i=0; i < nropts; i++ ) { + if ( opts[i].optword && (( sz = strlen(opts[i].optword) ) > maxoptwidth ) ) + maxoptwidth = sz; + if ( opts[i].opthasarg && (( sz = strlen(opts[i].opthasarg) ) > maxargwidth ) ) + maxargwidth = sz; + if ( opts[i].optchar ) + hasoptchar = 1; + } + + for ( i=0; i < nropts; i++ ) { + if ( opts[i].optword ) { + fprintf(stderr, " -%s", opts[i].optword); + j = strlen(opts[i].optword); + } + else j = -2; + + while ( j++ < maxoptwidth ) + fputc(' ', stderr); + + if ( opts[i].optchar ) + fprintf(stderr, " -%c ", opts[i].optchar); + else if ( hasoptchar ) + fprintf(stderr, " "); + + if ( maxargwidth > 0 ) { + if ( opts[i].opthasarg ) { + fprintf(stderr, " [%s]", opts[i].opthasarg); + j = strlen(opts[i].opthasarg); + } + else { + fprintf(stderr, " "); + j = 0; + } + + while ( j++ < maxargwidth ) + fputc(' ', stderr); + } + + if ( opts[i].optdesc ) + fprintf(stderr, " %s", opts[i].optdesc); + + fputc('\n', stderr); + } + } + else { + int optcount; + + for ( optcount=i=0; i < nropts; i++ ) { + if ( opts[i].optchar && !opts[i].opthasarg) { + if (optcount == 0 ) + fputs(" [-", stderr); + fputc(opts[i].optchar, stderr); + optcount++; + } + } + if ( optcount ) + fputc(']', stderr); + + /* print out the options WITH flags */ + for ( i = 0; i < nropts; i++ ) + if ( opts[i].optchar && opts[i].opthasarg) + fprintf(stderr, " [-%c %s]", opts[i].optchar, opts[i].opthasarg); + + /* print out the long options */ + for ( i = 0; i < nropts; i++ ) + if ( opts[i].optword ) { + fprintf(stderr, " [-%s", opts[i].optword); + if ( opts[i].opthasarg ) + fprintf(stderr, " %s", opts[i].opthasarg); + fputc(']', stderr); + } + + if ( arguments ) + fprintf(stderr, " %s", arguments); + } + + /* and we're done */ + fputc('\n', stderr); +} + + +void +hoptusage(char *pgm, struct h_opt opts[], int nropts, char *arguments) +{ + hoptdescribe(pgm, opts, nropts, arguments, 0); +} + + +#if DEBUG +struct h_opt opts[] = { + { 0, "css", 0, 1, "css file" }, + { 1, "header", 0, 1, "header file" }, + { 2, 0, 'a', 0, "option a (no arg)" }, + { 3, 0, 'b', 1, "option B (with arg)" }, + { 4, "help", '?', 0, "help message" }, +} ; + +#define NROPT (sizeof opts/sizeof opts[0]) + + +int +main(int argc, char **argv) +{ + struct h_opt *ret; + struct h_context ctx; + int i; + + + hoptset(&ctx, argc, argv); + hopterr(&ctx, 1); + + while (( ret = gethopt(&ctx, opts, NROPT) )) { + + if ( ret != HOPTERR ) { + if ( ret->optword ) + printf("%s", ret->optword); + else + printf("%c", ret->optchar); + + if ( ret->opthasarg ) { + if ( hoptarg(&ctx) ) + printf(" with argument \"%s\"", hoptarg(&ctx)); + else + printf(" with no argument?"); + } + printf(" (%s)\n", ret->optdesc); + } + } + + argc -= hoptind(&ctx); + argv += hoptind(&ctx); + + for ( i=0; i < argc; i++ ) + printf("%d: %s\n", i, argv[i]); + return 0; +} + +#endif /*DEBUG*/ diff --git a/gethopt.h b/gethopt.h new file mode 100644 index 0000000..b6ca050 --- /dev/null +++ b/gethopt.h @@ -0,0 +1,44 @@ +/* + * gethopt; options processing with both single-character and whole-work + * options both introduced with - + */ + +#ifndef __GETHOPT_D +#define __GETHOPT_D + +#include +#include + + +struct h_opt { + int option; + char *optword; + char optchar; + char *opthasarg; + char *optdesc; +} ; + +#define HOPTERR ((struct h_opt*)-1) + +struct h_context { + char **argv; + int argc; + int optchar; + int optind; + char *optarg; + char optopt; + int opterr:1; + int optend:1; +} ; + +extern char *hoptarg(struct h_context *); +extern int hoptind(struct h_context *); +extern char hoptopt(struct h_context *); +extern void hoptset(struct h_context *, int, char **); +extern int hopterr(struct h_context *, int); +extern struct h_opt *gethopt(struct h_context *, struct h_opt*, int); + +extern void hoptusage(char *, struct h_opt*, int, char *); +extern void hoptdescribe(char *, struct h_opt*, int, char *, int); + +#endif/*__GETHOPT_D*/ diff --git a/github_flavoured.c b/github_flavoured.c new file mode 100644 index 0000000..806c1bf --- /dev/null +++ b/github_flavoured.c @@ -0,0 +1,103 @@ + +/* + * github_flavoured -- implement the obnoxious "returns are hard newlines" + * feature in github flavoured markdown. + * + * Copyright (C) 2012 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include "config.h" +#include +#include +#include + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" + +/* build a Document from any old input. + */ +typedef int (*getc_func)(void*); + +Document * +gfm_populate(getc_func getc, void* ctx, mkd_flag_t *flags) +{ + Cstring line; + Document *a = __mkd_new_Document(); + int c; + int pandoc = 0; + + if ( !a ) return 0; + + if ( is_flag_set(flags, MKD_TABSTOP) || is_flag_set(flags, MKD_STRICT) ) + a->tabstop = 4; + else + a->tabstop = TABSTOP; + + CREATE(line); + + while ( (c = (*getc)(ctx)) != EOF ) { + if ( c == '\n' ) { + if ( pandoc != EOF && pandoc < 3 ) { + if ( S(line) && (T(line)[0] == '%') ) + pandoc++; + else + pandoc = EOF; + } + + if (pandoc == EOF) { + EXPAND(line) = ' '; + EXPAND(line) = ' '; + } + __mkd_enqueue(a, &line); + S(line) = 0; + } + else if ( isprint(c) || isspace(c) || (c & 0x80) ) + EXPAND(line) = c; + } + + if ( S(line) ) + __mkd_enqueue(a, &line); + + DELETE(line); + + if ( (pandoc == 3) && !is_flag_set(flags, MKD_NOHEADER) ) { + /* the first three lines started with %, so we have a header. + * clip the first three lines out of content and hang them + * off header. + */ + Line *headers = T(a->content); + + a->title = headers; __mkd_trim_line(a->title, 1); + a->author= headers->next; __mkd_trim_line(a->author, 1); + a->date = headers->next->next; __mkd_trim_line(a->date, 1); + + T(a->content) = headers->next->next->next; + } + + return a; +} + + +/* convert a block of text into a linked list + */ +Document * +gfm_string(const char *buf, int len, mkd_flag_t* flags) +{ + struct string_stream about; + + about.data = buf; + about.size = len; + + return gfm_populate((getc_func)__mkd_io_strget, &about, flags); +} + + +/* convert a file into a linked list + */ +Document * +gfm_in(FILE *f, mkd_flag_t* flags) +{ + return gfm_populate((getc_func)fgetc, f, flags); +} diff --git a/h1title.c b/h1title.c new file mode 100644 index 0000000..a6554be --- /dev/null +++ b/h1title.c @@ -0,0 +1,38 @@ +#include +#include "markdown.h" + +static Paragraph * +mkd_h1(Paragraph *p) +{ + Paragraph *found; + + while ( p ) { + if ( p->typ == HDR && p->hnumber == 1 ) + return p; + if ( p->down && (found = mkd_h1(p->down)) ) + return found; + p = p->next; + } + return 0; +} + +char * +mkd_h1_title(Document *doc) +{ + Paragraph *title; + + if (doc && (title = mkd_h1(doc->code)) ) { + char *generated; + int size; + + /* assert that a H1 header is one line long, so that's + * the only thing needed + */ + set_mkd_flag(&(doc->ctx->flags), MKD_TAGTEXT); + size = mkd_line(T(title->text->text), + S(title->text->text), &generated, &(doc->ctx->flags)); + clear_mkd_flag(&(doc->ctx->flags), MKD_TAGTEXT); + if ( size ) return generated; + } + return 0; +} diff --git a/html5.c b/html5.c new file mode 100644 index 0000000..2e05d04 --- /dev/null +++ b/html5.c @@ -0,0 +1,16 @@ +/* block-level tags for passing html5 blocks through the blender + */ +#include +#include "markdown.h" +#include "tags.h" + +void +mkd_add_html5_tags(MMIOT* doc) +{ + mkd_define_tag(doc, "ASIDE", 0); + mkd_define_tag(doc, "FOOTER", 0); + mkd_define_tag(doc, "HEADER", 0); + mkd_define_tag(doc, "NAV", 0); + mkd_define_tag(doc, "SECTION", 0); + mkd_define_tag(doc, "ARTICLE", 0); +} diff --git a/libmarkdown.pc.in b/libmarkdown.pc.in new file mode 100644 index 0000000..1d1082e --- /dev/null +++ b/libmarkdown.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@prefix@ +libdir=@libdir@ +includedir=@prefix@/include + +Name: @PACKAGE_NAME@ +Version: @PACKAGE_VERSION@ +Description: C implementation of John Gruber's Markdown markup language + +Libs: -L${libdir} -lmarkdown @LIBS@ +Cflags: -I${includedir} diff --git a/main.c b/main.c new file mode 100644 index 0000000..c7a855c --- /dev/null +++ b/main.c @@ -0,0 +1,378 @@ +/* + * markdown: convert a single markdown document into html + */ +/* + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "amalloc.h" +#include "pgm_options.h" +#include "tags.h" +#include "gethopt.h" + +#if HAVE_LIBGEN_H +#include +#endif + +#ifndef HAVE_BASENAME +char* +basename(char *p) +{ + char *ret = strrchr(p, '/'); + + return ret ? (1+ret) : p; +} +#endif + + +char *pgm = "markdown"; + +char * +e_flags(const char *text, const int size, void *context) +{ + return (char*)context; +} + + +void +complain(char *fmt, ...) +{ + va_list ptr; + + fprintf(stderr, "%s: ", pgm); + va_start(ptr, fmt); + vfprintf(stderr, fmt, ptr); + va_end(ptr); + fputc('\n', stderr); + fflush(stderr); +} + + +void +callback_free(char *input, int size, void *ctx) +{ + if (input) + free(input); +} + + +char * +anchor_format(char *input, void *ctx) +{ + int i, j, size; + char* ret; + + if ( !input ) + return NULL; + + size = strlen(input); + + ret = malloc(1+size); + + if ( !ret ) + return NULL; + + + while ( size && isspace(input[size-1]) ) + --size; + + for ( j=i=0; i < size; i++ ) { + if (isalnum(input[i]) || strchr("-_+", input[i]) ) + ret[j++] = input[i]; + else if ( input[i] == ' ' ) + ret[j++] = '-'; + } + ret[j++] = 0; + + return ret; +} + +char *external_formatter = 0; + + +#define RECEIVER 0 +#define SENDER 1 + +char * +external_codefmt(char *src, int len, char *lang) +{ + int child_status; + int size, bufsize, curr; + char *res; + + pid_t child; + + int tochild[2], toparent[2]; + + if ( pipe(tochild) != 0 || pipe(toparent) != 0 ) { + perror("external_codefmt (pipe)"); + res = malloc(len+1); + strcpy(res, src); + return res; + } + + if ( (child = fork()) > 0 ) { + + close(tochild[RECEIVER]); + close(toparent[SENDER]); + + bufsize = 1000; + res = malloc(1+bufsize); + curr = 0; + + /* parent */ + write(tochild[SENDER], src, len); + close(tochild[SENDER]); + + while ( (size = read(toparent[RECEIVER], res+curr, 1000)) > 0 ) { + curr += size; + res = realloc(res, bufsize += 1000); + } + res[curr] = 0; + waitpid(child, &child_status, 0); + + close(toparent[RECEIVER]); + + if ( WIFEXITED(child_status) ) + return res; + else + free(res); /* something failed; just return the original string */ + } + else if ( child == 0 ) { + close(tochild[SENDER]); + close(toparent[RECEIVER]); + close(1); dup2(toparent[SENDER], 1); + close(0); dup2(tochild[RECEIVER], 0); + system(external_formatter); + close(0); close(tochild[RECEIVER]); + close(1); close(toparent[SENDER]); + exit(0); + } + res = malloc(len+1); + strcpy(res, src); + return res; +} + + +struct h_opt opts[] = { + { 0, "html5", '5', 0, "recognise html5 block elements" }, + { 0, "base", 'b', "url-base", "URL prefix" }, + { 0, "debug", 'd', 0, "debugging" }, + { 0, "version",'V', 0, "show version info" }, + { 0, 0, 'E', "flags", "url flags" }, + { 0, 0, 'F', "bitmap", "set/show hex flags" }, + { 0, 0, 'f', "{+-}flags", "set/show named flags" }, + { 0, 0, 'G', 0, "github flavoured markdown" }, + { 0, 0, 'n', 0, "don't write generated html" }, + { 0, 0, 's', "text", "format `text`" }, + { 0, "style", 'S', 0, "output +at the end of the line or a +.Em +at the beginning of a subsequent line. +.Pp +Be warned that style blocks work like footnote links -- no matter +where you define them they are valid for the entire document. +.Ss alpha lists +Alphabetic lists (like regular numeric lists, but with alphabetic +items) are supported. So: +.nf + a. this + b. is + c. an alphabetic + d. list +.fi +will produce: +.nf +
    +
  1. this
  2. +
  3. is
  4. +
  5. an alphabetic
  6. +
  7. list
  8. +
+.fi +.Ss tables +.Ar "PHP Markdown Extra" +tables are supported; input of the form +.nf + header|header + ------|------ + text | text +.fi +will produce: +.nf + + + + + + + + + + + + + +
headerheader
texttext
+.fi +The dashed line can also contain +.Em : +characters for formatting; if a +.Em : +is at the start of a column, it tells +.Nm discount +to align the cell contents to the left; if it's at the end, it +aligns right, and if there's one at the start and at the +end, it centers. +.Ss strikethrough +A strikethrough syntax is supported in much the same way that +.Ar ` +is used to define a section of code. If you enclose text with +two or more tildes, such as +.Em ~~erased text~~ +it will be written as +.Em "erased text" . +Like code sections, you may use as many +.Ar ~ +as you want, but there must be as many starting tildes as closing +tildes. +.Ss markdown extra-style footnotes +.Ar "PHP Markdown Extra" +footnotes are supported. If a footnote link begins with a +.Ar ^ , +the first use of that footnote will generate a link down to the +bottom of the rendered document, which will contain a numbered footnote +with a link back to where the footnote was called. +.Sh AUTHOR +Jessica L. Parsons +.%T http://www.pell.portland.or.us/~orc/ +.Sh SEE ALSO +.Xr markdown 1 , +.Xr markdown 3 , +.Xr mkd-callbacks 3 , +.Xr mkd-functions 3 , +.Xr mkd-line 3 . +.Pp +.%T http://daringfireball.net/projects/markdown +.Pp +.%T http://michelf.com/projects/php-markdown diff --git a/mkd-functions.3 b/mkd-functions.3 new file mode 100644 index 0000000..bcda899 --- /dev/null +++ b/mkd-functions.3 @@ -0,0 +1,186 @@ +.\" +.Dd January 18, 2008 +.Dt MKD_FUNCTIONS 3 +.Os Mastodon +.Sh NAME +.Nm mkd_functions +.Nd access and process Markdown documents. +.Sh LIBRARY +Markdown +.Pq libmarkdown , -lmarkdown +.Sh SYNOPSIS +.Fd #include +.Ft int +.Fn mkd_compile "MMIOT *document" "mkd_flag_t *flags" +.Ft int +.Fn mkd_css "MMIOT *document" "char **doc" +.Ft int +.Fn mkd_generatecss "MMIOT *document" "FILE *output" +.Ft int +.Fn mkd_document "MMIOT *document" "char **doc" +.Ft int +.Fn mkd_generatehtml "MMIOT *document" "FILE *output" +.Ft int +.Fn mkd_xhtmlpage "MMIOT *document" "mkd_flag_t *flags" "FILE *output" +.Ft int +.Fn mkd_toc "MMIOT *document" "char **doc" +.Ft void +.Fn mkd_generatetoc "MMIOT *document" "FILE *output" +.Ft void +.Fn mkd_cleanup "MMIOT*" +.Ft char* +.Fn mkd_doc_title "MMIOT*" +.Ft char* +.Fn mkd_doc_author "MMIOT*" +.Ft char* +.Fn mkd_doc_date "MMIOT*" +.Sh DESCRIPTION +.Pp +The +.Nm markdown +format supported in this implementation includes +Pandoc-style header and inline +.Ar \ +blocks, and the standard +.Xr markdown 3 +functions do not provide access to +the data provided by either of those extensions. +These functions give you access to that data, plus +they provide a finer-grained way of converting +.Em Markdown +documents into HTML. +.Pp +Given a +.Ar MMIOT* +generated by +.Fn mkd_in +or +.Fn mkd_string , +.Fn mkd_compile +compiles the document into +.Em \ , +.Em Pandoc , +and +.Em html +sections. +.Pp +Once compiled, the document can be examined and written +by the +.Fn mkd_css , +.Fn mkd_document , +.Fn mkd_generatecss , +.Fn mkd_generatehtml , +.Fn mkd_generatetoc , +.Fn mkd_toc , +.Fn mkd_xhtmlpage , +.Fn mkd_doc_title , +.Fn mkd_doc_author , +and +.Fn mkd_doc_date +functions. +.Pp +.Fn mkd_css +allocates a string and populates it with any \ sections +provided in the document, +.Fn mkd_generatecss +writes any \ sections to the output, +.Fn mkd_document +points +.Ar text +to the text of the document and returns the +size of the document, +.Fn mkd_generatehtml +writes the rest of the document to the output, +and +.Fn mkd_doc_title , +.Fn mkd_doc_author , +.Fn mkd_doc_date +are used to read the contents of a Pandoc header, +if any. +.Pp +.Fn mkd_xhtmlpage +writes a xhtml page containing the document. The regular set of +flags can be passed. +.Pp +.Fn mkd_toc +writes a document outline, in the form of a collection of nested +lists with links to each header in the document, into a string +allocated with +.Fn malloc , +and returns the size. +.Pp +.Fn mkd_generatetoc +is like +.Fn mkd_toc , +except that it writes the document outline to the given +.Pa FILE* +argument. +.Pp +.Fn mkd_cleanup +deletes a +.Ar MMIOT* +after processing is done. +.Pp +.Fn mkd_compile +accepts the same flags that +.Fn markdown +and +.Fn mkd_string +do; +.Bl -tag -width MKD_NOSTRIKETHROUGH -compact +.It Ar MKD_NOIMAGE +Do not process `![]' and +remove +.Em \ +tags from the output. +.It Ar MKD_NOLINKS +Do not process `[]' and remove +.Em \ +tags from the output. +.It Ar MKD_NOPANTS +Do not do Smartypants-style mangling of quotes, dashes, or ellipses. +.It Ar MKD_TAGTEXT +Process the input as if you were inside a html tag. This means that +no html tags will be generated, and +.Fn mkd_compile +will attempt to escape anything that might terribly confuse a +web browser. +.It Ar MKD_NO_EXT +Do not process any markdown pseudo-protocols when +handing +.Ar [][] +links. +.It Ar MKD_NOHEADER +Do not attempt to parse any Pandoc-style headers. +.It Ar MKD_TOC +Label all headers for use with the +.Fn mkd_generatetoc +function. +.It Ar MKD_1_COMPAT +MarkdownTest_1.0 compatibility flag; trim trailing spaces from the +first line of code blocks and disable implicit reference links. +.It Ar MKD_NOSTRIKETHROUGH +Disable strikethrough support. +.El +.Sh RETURN VALUES +The function +.Fn mkd_compile +returns 1 in the case of success, or 0 if the document is already compiled. +The function +.Fn mkd_generatecss +returns the number of bytes written in the case of success, or EOF if an error +occurred. +The function +.Fn mkd_generatehtml +returns 0 on success, \-1 on failure. +.Sh SEE ALSO +.Xr markdown 1 , +.Xr markdown 3 , +.Xr mkd-line 3 , +.Xr markdown 7 , +.Xr mkd-extensions 7 , +.Xr mmap 2 . +.Pp +http://daringfireball.net/projects/markdown/syntax +.Sh BUGS +Error handling is minimal at best. diff --git a/mkd-line.3 b/mkd-line.3 new file mode 100644 index 0000000..dc8ef02 --- /dev/null +++ b/mkd-line.3 @@ -0,0 +1,41 @@ +.\" +.Dd January 18, 2008 +.Dt MKD_LINE 3 +.Os Mastodon +.Sh NAME +.Nm mkd_line +.Nd do Markdown translation of small items +.Sh LIBRARY +Markdown +.Pq libmarkdown , -lmarkdown +.Sh SYNOPSIS +.Fd #include +.Ft int +.Fn mkd_line "char *string" "int size" "char **doc" "mkd_flag_t *flags" +.Ft int +.Fn mkd_generateline "char *string" "int size" "FILE *output" "mkd_flag_t *flags" +.Sh DESCRIPTION +.Pp +Occasionally one might want to do markdown translations on fragments of +data, like the title of an weblog article, a date, or a simple signature +line. +.Nm mkd_line +and +.Nm mkd_generateline +allow you to do markdown translations on small blocks of text. +.Nm mkd_line +allocates a buffer, then writes the translated text into that buffer, +and +.Nm mkd_generateline +writes the output to the specified +.Ar FILE* . +.Sh SEE ALSO +.Xr markdown 1 , +.Xr markdown 3 , +.Xr markdown 7 , +.Xr mkd-extensions 7 , +.Xr mmap 2 . +.Pp +http://daringfireball.net/projects/markdown/syntax +.Sh BUGS +Error handling is minimal at best. diff --git a/mkd2html.1 b/mkd2html.1 new file mode 100644 index 0000000..f977c72 --- /dev/null +++ b/mkd2html.1 @@ -0,0 +1,52 @@ +.\" %A% +.\" +.Dd January 10, 2010 +.Dt MKD2HTML 1 +.Os MASTODON +.Sh NAME +.Nm mkd2html +.Nd markdown to html converter +.Sh SYNOPSIS +.Nm +.Op Fl css Pa file +.Op Fl header Pa string +.Op Fl footer Pa string +.Op Pa file +.Sh DESCRIPTION +.Nm +utility parses a +.Xr markdown 7 Ns -formatted +.Pa textfile +.Pq or stdin if not specified, +and generates a web page. It +reads +.Ar file +or +.Ar file.text + and writes the result in +.Ar file.html +.Pq where file is the passed argument. +.Pp +.Nm +is part of discount. +.Sh OPTIONS +.Bl -tag -width "-header string" +.It Fl css Ar file +Specifies a CSS file. +.It Fl header Ar string +Specifies a line to add to the
tag. +.It Fl footer Ar string +Specifies a line to add before the <\/body> tag. +.El +.Sh RETURN VALUES +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr markdown 1 , +.Xr markdown 3 , +.Xr markdown 7 , +.Xr mkd-extensions 7 . +.Sh AUTHOR +.An Jessica L. Parsons +.Pq Li orc@pell.portland.or.us diff --git a/mkd2html.c b/mkd2html.c new file mode 100644 index 0000000..f0bc568 --- /dev/null +++ b/mkd2html.c @@ -0,0 +1,235 @@ +/* + * mkd2html: parse a markdown input file and generate a web page. + * + * usage: mkd2html [options] filename + * or mkd2html [options] < markdown > html + * + * options + * -css css-file + * -header line-to-add-to-
+ * -footer line-to-add-before- + * + * example: + * + * mkd2html -css /~orc/pages.css syntax + * ( read syntax OR syntax.text, write syntax.html ) + */ +/* + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include "config.h" + +#include +#include +#include +#ifdef HAVE_BASENAME +# ifdef HAVE_LIBGEN_H +# include +# else +# include +# endif +#endif +#include + +#include "mkdio.h" +#include "cstring.h" +#include "amalloc.h" + +#include "gethopt.h" + +char *pgm = "mkd2html"; + +extern int notspecial(char *filename); + +#ifndef HAVE_BASENAME +char * +basename(char *path) +{ + char *p; + + if ( p = strrchr(path, '/') ) + return 1+p; + return path; +} +#endif + +void +fail(char *why, ...) +{ + va_list ptr; + + va_start(ptr,why); + fprintf(stderr, "%s: ", pgm); + vfprintf(stderr, why, ptr); + fputc('\n', stderr); + va_end(ptr); + exit(1); +} + + +enum { GFM, ADD_CSS, ADD_HEADER, ADD_FOOTER }; + +struct h_opt opts[] = { + { GFM, "gfm",'G', 0, "Github style markdown" }, + { ADD_CSS, "css", 0, "url", "Additional css for this page" }, + { ADD_HEADER, "header", 0, "header", "Additional headers for this page" }, + { ADD_FOOTER, "footer", 0, "footer", "Additional footers for this page" }, +}; +#define NROPTS (sizeof opts/sizeof opts[0]) + +#if USE_H1TITLE +extern char* mkd_h1_title(MMIOT *); +#endif + + +int +main(int argc, char **argv) +{ + char *h; + char *source = 0, *dest = 0; + MMIOT *mmiot; + int i; + int gfm = 0; + FILE *input, *output; + STRING(char*) css, headers, footers; + struct h_opt *res; + struct h_context flags; + + + CREATE(css); + CREATE(headers); + CREATE(footers); + pgm = basename(argv[0]); + + hoptset(&flags, argc, argv); + hopterr(&flags, 1); + while ( res = gethopt(&flags, opts, NROPTS) ) { + if ( res == HOPTERR ) { + hoptusage(pgm, opts, NROPTS, "source [dest]"); + exit(1); + } + + switch ( res->option ) { + case ADD_CSS: + EXPAND(css) = hoptarg(&flags); + break; + case ADD_HEADER: + EXPAND(headers) = hoptarg(&flags); + break; + case ADD_FOOTER: + EXPAND(footers) = hoptarg(&flags); + break; + case GFM: + gfm = 1; + break; + default: + fprintf(stderr, "unknown option?\n"); + break; + } + } + + argc -= hoptind(&flags); + argv += hoptind(&flags); + + switch ( argc ) { + char *p, *dot; + case 0: + input = stdin; + output = stdout; + break; + + case 1: + case 2: + dest = malloc(strlen(argv[argc-1]) + 6); + source = malloc(strlen(argv[0]) + 6); + + if ( !(source && dest) ) + fail("out of memory allocating name buffers"); + + strcpy(source, argv[0]); + strcpy(dest, argv[argc-1]); + if (( p = strrchr(source, '/') )) + p = source; + else + ++p; + + if ( (input = fopen(source, "r")) == 0 ) { + strcat(source, ".text"); + if ( (input = fopen(source, "r")) == 0 ) + fail("can't open either %s or %s", argv[0], source); + } + + if ( notspecial(dest) ) { + if (( dot = strrchr(dest, '.') )) + *dot = 0; + strcat(dest, ".html"); + } + + if ( (output = fopen(dest, "w")) == 0 ) + fail("can't write to %s", dest); + break; + + default: + hoptusage(pgm, opts, NROPTS, "source [dest]"); + exit(1); + } + + mmiot = gfm ? gfm_in(input, 0) : mkd_in(input, 0); + + if ( mmiot == 0 ) + fail("can't read %s", source ? source : "stdin"); + + if ( !mkd_compile(mmiot, 0) ) + fail("couldn't compile input"); + + + h = mkd_doc_title(mmiot); +#if USE_H1TITLE + if ( ! h ) + h = mkd_h1_title(mmiot); +#endif + + /* print a header */ + + fprintf(output, + "\n" + "\n" + "\n" + " \n", markdown_version); + + fprintf(output," \n"); + + for ( i=0; i < S(css); i++ ) + fprintf(output, " \n", T(css)[i]); + + fprintf(output," "); + if ( h ) + mkd_generateline(h, strlen(h), output, 0); + /* xhtml requires a <title> in the header, even if it doesn't + * contain anything + */ + fprintf(output, "\n"); + + for ( i=0; i < S(headers); i++ ) + fprintf(output, " %s\n", T(headers)[i]); + fprintf(output, "\n" + "\n"); + + /* print the compiled body */ + + mkd_generatehtml(mmiot, output); + + for ( i=0; i < S(footers); i++ ) + fprintf(output, "%s\n", T(footers)[i]); + + fprintf(output, "\n" + "\n"); + + mkd_cleanup(mmiot); + exit(0); +} diff --git a/mkdio.c b/mkdio.c new file mode 100644 index 0000000..362d13a --- /dev/null +++ b/mkdio.c @@ -0,0 +1,527 @@ +/* + * mkdio -- markdown front end input functions + * + * Copyright (C) 2007 Jessica L Parsons. + * The redistribution terms are provided in the COPYRIGHT file that must + * be distributed with this source code. + */ +#include "config.h" +#include +#include +#include + +#include "cstring.h" +#include "markdown.h" +#include "amalloc.h" +#include "tags.h" + +typedef ANCHOR(Line) LineAnchor; + + +/* create a new blank Document + */ +Document* +__mkd_new_Document(void) +{ + Document *ret = calloc(sizeof(Document), 1); + + if ( ret ) { + if ( ret->ctx = calloc(sizeof(MMIOT), 1) ) { + ret->magic = VALID_DOCUMENT; + return ret; + } + free(ret); + } + return 0; +} + + +/* add a line to the markdown input chain, expanding tabs and + * noting the presence of special characters as we go. + */ +void +__mkd_enqueue(Document* a, Cstring *line) +{ + Line *p = calloc(sizeof *p, 1); + unsigned char c; + int xp = 0; + int size = S(*line); + unsigned char *str = (unsigned char*)T(*line); + + CREATE(p->text); + ATTACH(a->content, p); + + while ( size-- ) { + if ( (c = *str++) == '\t' ) { + /* expand tabs into ->tabstop spaces. We use ->tabstop + * because the ENTIRE FREAKING COMPUTER WORLD uses editors + * that don't do ^T/^D, but instead use tabs for indentation, + * and, of course, set their tabs down to 4 spaces + */ + do { + EXPAND(p->text) = ' '; + } while ( ++xp % a->tabstop ); + } + else if ( c >= ' ' ) { + if ( c == '|' ) + p->has_pipechar = 1; + EXPAND(p->text) = c; + ++xp; + } + } + COMPLETE(p->text); + p->dle = mkd_firstnonblank(p); +} + + +/* trim leading characters from a line, then adjust the dle. + */ +void +__mkd_trim_line(Line *p, int clip) +{ + if ( clip >= S(p->text) ) { + S(p->text) = p->dle = 0; + T(p->text)[0] = 0; + } + else if ( clip > 0 ) { + CLIP(p->text, 0, clip); + p->dle = mkd_firstnonblank(p); + } +} + + +/* build a Document from any old input. + */ +typedef int (*getc_func)(void*); + +Document * +populate(getc_func getc, void* ctx, mkd_flag_t *flags) +{ + Cstring line; + Document *a = __mkd_new_Document(); + int c; + int pandoc = 0; + + if ( flags && (is_flag_set(flags, MKD_NOHEADER) || is_flag_set(flags, MKD_STRICT)) ) + pandoc= EOF; + + if ( !a ) return 0; + + + if ( flags && (is_flag_set(flags, MKD_TABSTOP) || is_flag_set(flags, MKD_STRICT)) ) + a->tabstop = 4; + else + a->tabstop = TABSTOP; + + CREATE(line); + + while ( (c = (*getc)(ctx)) != EOF ) { + if ( c == '\n' ) { + if ( pandoc != EOF && pandoc < 3 ) { + if ( S(line) && (T(line)[0] == '%') ) + pandoc++; + else + pandoc = EOF; + } + __mkd_enqueue(a, &line); + S(line) = 0; + } + else if ( (c & 0x80) || isprint(c) || isspace(c) ) + EXPAND(line) = c; + } + + if ( S(line) ) + __mkd_enqueue(a, &line); + + DELETE(line); + + if ( pandoc == 3 ) { + /* the first three lines started with %, so we have a header. + * clip the first three lines out of content and hang them + * off header. + */ + Line *headers = T(a->content); + + a->title = headers; __mkd_trim_line(a->title, 1); + a->author= headers->next; __mkd_trim_line(a->author, 1); + a->date = headers->next->next; __mkd_trim_line(a->date, 1); + + T(a->content) = headers->next->next->next; + } + + return a; +} + + +/* convert a file into a linked list + */ +Document * +mkd_in(FILE *f, mkd_flag_t *flags) +{ + return populate((getc_func)fgetc, f, flags); +} + + +/* return a single character out of a buffer + */ +int +__mkd_io_strget(struct string_stream *in) +{ + if ( in->size > 0 ) { + --(in->size); + return *(in->data)++; + } + return EOF; +} + + +/* convert a block of text into a linked list + */ +Document * +mkd_string(const char *buf, int len, mkd_flag_t* flags) +{ + struct string_stream about; + + about.data = buf; + about.size = len; + + return populate((getc_func)__mkd_io_strget, &about, flags); +} + + +/* write the html to a file (xmlified if necessary) + */ +int +mkd_generatehtml(Document *p, FILE *output) +{ + char *doc; + int szdoc; + + DO_OR_DIE( szdoc = mkd_document(p,&doc) ); + if ( is_flag_set( &(p->ctx->flags), MKD_CDATA ) ) + DO_OR_DIE( mkd_generatexml(doc, szdoc, output) ); + else if ( fwrite(doc, szdoc, 1, output) != 1 ) + return EOF; + DO_OR_DIE( putc('\n', output) ); + return 0; +} + + +/* convert some markdown text to html + */ +int +markdown(Document *document, FILE *out, mkd_flag_t* flags) +{ + if ( mkd_compile(document, flags) ) { + mkd_generatehtml(document, out); + mkd_cleanup(document); + return 0; + } + return -1; +} + + +/* anchor_format a string, returning the formatted string in malloc()ed space + * MKD_URLENCODEDANCHOR is now perverted to being a html5 anchor + * + * !labelformat: print all characters + * labelformat && h4anchor: prefix nonalpha label with L, + * expand all nonalnum, _, ':', '.' to hex + * except space which maps to - + * labelformat && !h4anchor:expand space to -, other isspace() & '%' to hex + */ +static char * +mkd_anchor_format(char *s, int len, int labelformat, mkd_flag_t *flags) +{ + char *res; + unsigned char c; + int i, needed, out = 0; + int h4anchor = !is_flag_set(flags, MKD_URLENCODEDANCHOR); + static const unsigned char hexchars[] = "0123456789abcdef"; + + needed = (labelformat ? (4*len) : len) + 2; /* +2 for L & \0 */ + + if ( (res = malloc(needed)) == NULL ) + return NULL; + + if ( h4anchor && labelformat && !isalpha(s[0]) ) + res[out++] = 'L'; + + + for ( i=0; i < len ; i++ ) { + c = s[i]; + if ( labelformat ) { + if ( h4anchor + ? (isalnum(c) || (c == '_') || (c == ':') || (c == '.' ) ) + : !(isspace(c) || c == '%') ) + res[out++] = c; + else if ( c == ' ' ) + res[out++] = '-'; + else { + res[out++] = h4anchor ? '-' : '%'; + res[out++] = hexchars[c >> 4 & 0xf]; + res[out++] = hexchars[c & 0xf]; + if ( h4anchor ) + res[out++] = '-'; + } + } + else + res[out++] = c; + } + + res[out++] = 0; + return res; +} /* mkd_anchor_format */ + + +/* write out a Cstring, mangled into a form suitable for `cb->e_anchor.func ) + res = (*(f->cb->e_anchor.func))(line, size, f->cb->e_anchor.data); + else + res = mkd_anchor_format(line, size, labelformat, &(f->flags)); + + free(line); + + if ( res ) { + for ( i=0; res[i]; i++ ) + (*outchar)(res[i], out); + + if ( f->cb->e_anchor.free ) + (*f->cb->e_anchor.free)(res, i, f); + else + free(res); + } +} + + +/* ___mkd_reparse() a line + */ +static void +mkd_parse_line(char *bfr, int size, MMIOT *f, mkd_flag_t *flags) +{ + ___mkd_initmmiot(f, 0, flags); + ___mkd_reparse(bfr, size, NULL, f, 0); + ___mkd_emblock(f); +} + + +/* ___mkd_reparse() a line, returning it in malloc()ed memory + */ +int +mkd_line(char *bfr, int size, char **res, mkd_flag_t* flags) +{ + MMIOT f; + int len; + + mkd_parse_line(bfr, size, &f, flags); + + if ( len = S(f.out) ) { + COMPLETE(f.out); + /* strdup() doesn't use amalloc(), so in an amalloc()ed + * build this copies the string safely out of our memory + * paranoia arena. In a non-amalloc world, it's a spurious + * memory allocation, but it avoids unintentional hilarity + * with amalloc() + */ + *res = strdup(T(f.out)); + } + else { + *res = 0; + len = EOF; + } + ___mkd_freemmiot(&f, 0); + return len; +} + + +/* ___mkd_reparse() a line, writing it to a FILE + */ +int +mkd_generateline(char *bfr, int size, FILE *output, mkd_flag_t* flags) +{ + MMIOT f; + int status; + + mkd_parse_line(bfr, size, &f, flags); + if ( flags && is_flag_set(flags, MKD_CDATA) ) + status = mkd_generatexml(T(f.out), S(f.out), output) != EOF; + else + status = fwrite(T(f.out), S(f.out), 1, output) == S(f.out); + + ___mkd_freemmiot(&f, 0); + return status ? 0 : EOF; +} + + +/* set the url display callback + */ +void +mkd_e_url(Document *f, mkd_callback_t edit, mkd_callback_t free, void *data) +{ + if ( f ) { + if ( f->cb.e_url.func != edit ) + f->dirty = 1; + f->cb.e_url.func = edit; + f->cb.e_url.data = data; + f->cb.e_url.free = free; + } +} + + +/* set the url options callback + */ +void +mkd_e_flags(Document *f, mkd_callback_t edit, mkd_callback_t free, void *data) +{ + if ( f ) { + if ( f->cb.e_flags.func != edit ) + f->dirty = 1; + f->cb.e_flags.func = edit; + f->cb.e_flags.free = free; + f->cb.e_flags.data = data; + } +} + + +/* set the anchor formatter + */ +void +mkd_e_anchor(Document *f, mkd_callback_t format, mkd_callback_t free, void *data) +{ + if ( f ) { + if ( f->cb.e_anchor.func != format ) + f->dirty = 1; + f->cb.e_anchor.func = format; + f->cb.e_anchor.free = free; + f->cb.e_anchor.data = data; + } +} + + +/* set the code block display callback + */ +void +mkd_e_code_format(Document *f, mkd_callback_t codefmt, mkd_callback_t free, void *data) +{ + if ( f && (f->cb.e_codefmt.func != codefmt) ) { + f->dirty = 1; + f->cb.e_codefmt.func = codefmt; + f->cb.e_codefmt.free = free; + f->cb.e_codefmt.data = data; + } +} + + +/* set the href prefix for markdown extra style footnotes + */ +void +mkd_ref_prefix(Document *f, char *data) +{ + if ( f ) { + if ( f->ref_prefix != data ) + f->dirty = 1; + f->ref_prefix = data; + } +} + +#if 0 +static void +sayflags(char *pfx, mkd_flag_t* flags, FILE *output) +{ + int i; + + fprintf(output, "%.*s/", (int)strlen(pfx), " "); + for (i=0; i + +@SCALAR_HEADER_INCLUDE@ + +typedef void MMIOT; + +/* special flags for markdown() and mkd_text() + */ +enum { MKD_NOLINKS=0, /* don't do link processing, block tags */ + MKD_NOIMAGE, /* don't do image processing, block */ + MKD_NOPANTS, /* don't run smartypants() */ + MKD_NOHTML, /* don't allow raw html through AT ALL */ + MKD_NORMAL_LISTITEM, /* disable github-style checkbox lists */ + MKD_TAGTEXT, /* process text inside an html tag */ + MKD_NO_EXT, /* don't allow pseudo-protocols */ +#define MKD_NOEXT MKD_NO_EXT + MKD_EXPLICITLIST, /* don't combine numbered/bulletted lists */ + MKD_CDATA, /* generate code for xml ![CDATA[...]] */ + MKD_NOSUPERSCRIPT, /* no A^B */ + MKD_STRICT, /* conform to Markdown standard as implemented in Markdown.pl */ + MKD_NOTABLES, /* disallow tables */ + MKD_NOSTRIKETHROUGH, /* forbid ~~strikethrough~~ */ + MKD_1_COMPAT, /* compatibility with MarkdownTest_1.0 */ + MKD_TOC, /* do table-of-contents processing */ + MKD_AUTOLINK, /* make http://foo.com link even without <>s */ + MKD_NOHEADER, /* don't process header blocks */ + MKD_TABSTOP, /* expand tabs to 4 spaces */ + MKD_SAFELINK, /* paranoid check for link protocol */ + MKD_NODIVQUOTE, /* forbid >%class% blocks */ + MKD_NOALPHALIST, /* forbid alphabetic lists */ + MKD_EXTRA_FOOTNOTE, /* enable markdown extra-style footnotes */ + MKD_NOSTYLE, /* don't extract ' '' + +ASK='' + +try 'multiple lines' "$ASK" '' + +try 'unclosed' '