[SCM] The rsync repository. - branch master updated

Rsync CVS commit messages rsync-cvs at lists.samba.org
Tue Jun 9 04:07:22 UTC 2020


The branch, master has been updated
       via  53fae556 Change man page src format from yodl to markdown.
      from  bd66a92e Tweak the new heading

https://git.samba.org/?p=rsync.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 53fae556521d035544ed8f0e968451ce6c60e664
Author: Wayne Davison <wayne at opencoder.net>
Date:   Mon Jun 8 19:21:42 2020 -0700

    Change man page src format from yodl to markdown.
    
    This removes the yodl dependency, which is sometimes hard to track down.
    Instead, this uses a python3 script that leverages the cmarkgfm library
    to turn the source file into html.  Then, the script parses the html in
    order to turn the tag stream into a nroff stream using a simple state
    machine. While it's doing that it also implements one added format rule
    that turns an ordinal list that starts at 0 into a description list
    (since markdown doesn't have an easy description list idiom).

-----------------------------------------------------------------------

Summary of changes:
 .github/workflows/ccpp.yml |    2 +-
 .gitignore                 |    5 +-
 Makefile.in                |   34 +-
 configure.ac               |   12 -
 maybe-make-man             |   37 +
 md2man                     |  314 ++++
 packaging/nightly-rsync    |   20 +-
 packaging/release-rsync    |   17 +-
 rsync-ssl.1.md             |   90 +
 rsync-ssl.yo               |   99 --
 rsync.1.md                 | 3905 ++++++++++++++++++++++++++++++++++++++++++++
 rsync.yo                   | 3722 -----------------------------------------
 rsyncd.conf.5.md           | 1126 +++++++++++++
 rsyncd.conf.yo             | 1063 ------------
 tweak_manpage              |   44 -
 15 files changed, 5500 insertions(+), 4990 deletions(-)
 create mode 100755 maybe-make-man
 create mode 100755 md2man
 create mode 100644 rsync-ssl.1.md
 delete mode 100644 rsync-ssl.yo
 create mode 100644 rsync.1.md
 delete mode 100644 rsync.yo
 create mode 100644 rsyncd.conf.5.md
 delete mode 100644 rsyncd.conf.yo
 delete mode 100755 tweak_manpage


Changeset truncated at 500 lines:

diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml
index a9e4a3f8..61ce2d8e 100644
--- a/.github/workflows/ccpp.yml
+++ b/.github/workflows/ccpp.yml
@@ -14,7 +14,7 @@ jobs:
     steps:
     - uses: actions/checkout at v2
     - name: prepare-packages
-      run: sudo apt-get install fakeroot acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev yodl
+      run: sudo apt-get install fakeroot acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm
     - name: prepare-source
       run: ./prepare-source
     - name: configure
diff --git a/.gitignore b/.gitignore
index de495172..8f289931 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,9 @@ config.status
 aclocal.m4
 /proto.h
 /proto.h-tstamp
-/*.1
-/*.5
+/rsync*.1
+/rsync*.5
+/rsync*.html
 /autom4te*.cache
 /confdefs.h
 /conftest*
diff --git a/Makefile.in b/Makefile.in
index b45f3f47..e7f2f644 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -33,7 +33,8 @@ VERSION=@RSYNC_VERSION@
 
 SIMD_x86_64=simd-checksum-x86_64.o lib/md5-asm-x86_64.o
 
-GENFILES=configure.sh aclocal.m4 config.h.in proto.h proto.h-tstamp rsync.1 rsync-ssl.1 rsyncd.conf.5
+GENFILES=configure.sh aclocal.m4 config.h.in proto.h proto.h-tstamp rsync.1 rsync.1.html \
+	 rsync-ssl.1 rsync-ssl.1.html rsyncd.conf.5 rsyncd.conf.5.html
 HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \
 	lib/pool_alloc.h
 LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
@@ -67,7 +68,7 @@ CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o trimslash.
 	$(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) -c $< @CC_SHOBJ_FLAG@
 @OBJ_RESTORE@
 
-all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf @MAKE_MAN@
+all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf man
 
 install: all
 	-${MKDIR_P} ${DESTDIR}${bindir}
@@ -214,31 +215,16 @@ proto.h: proto.h-tstamp
 proto.h-tstamp: $(srcdir)/*.c $(srcdir)/lib/compat.c config.h
 	awk -f $(srcdir)/mkproto.awk $(srcdir)/*.c $(srcdir)/lib/compat.c
 
-man: rsync.1 rsync-ssl.1 rsyncd.conf.5 man-copy
+man: rsync.1 rsync-ssl.1 rsyncd.conf.5
 
-man-copy:
-	@for fn in rsync.1 rsync-ssl.1 rsyncd.conf.5; do \
-	    if test -f $$fn; then \
-		: ; \
-	    elif test -f $(srcdir)/$$fn; then \
-		echo "Copying srcdir $$fn" ; \
-		cp -p $(srcdir)/$$fn . ; \
-	    else \
-		echo "NOTE: $$fn cannot be created." ; \
-	    fi ; \
-	done
-
-rsync.1: rsync.yo $(srcdir)/tweak_manpage
-	yodl2man -o rsync.1 $(srcdir)/rsync.yo
-	-$(srcdir)/tweak_manpage rsync.1
+rsync.1: rsync.1.md md2man latest-year.h Makefile
+	@$(srcdir)/maybe-make-man $(srcdir) rsync.1.md
 
-rsync-ssl.1: rsync-ssl.yo $(srcdir)/tweak_manpage
-	yodl2man -o rsync-ssl.1 $(srcdir)/rsync-ssl.yo
-	-$(srcdir)/tweak_manpage rsync-ssl.1
+rsync-ssl.1: rsync-ssl.1.md md2man latest-year.h Makefile
+	@$(srcdir)/maybe-make-man $(srcdir) rsync-ssl.1.md
 
-rsyncd.conf.5: rsyncd.conf.yo $(srcdir)/tweak_manpage
-	yodl2man -o rsyncd.conf.5 $(srcdir)/rsyncd.conf.yo
-	-$(srcdir)/tweak_manpage rsyncd.conf.5
+rsyncd.conf.5: rsyncd.conf.5.md md2man latest-year.h Makefile
+	@$(srcdir)/maybe-make-man $(srcdir) rsyncd.conf.5.md
 
 clean: cleantests
 	rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) \
diff --git a/configure.ac b/configure.ac
index c2771190..372399c2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -135,13 +135,6 @@ else
 fi
 AC_DEFINE_UNQUOTED(RSYNC_RSH, "$RSYNC_RSH", [default -e command])
 
-AC_CHECK_PROG(HAVE_YODL2MAN, yodl2man, 1, 0)
-if test x$HAVE_YODL2MAN = x1; then
-    MAKE_MAN=man
-else
-    MAKE_MAN=man-copy
-fi
-
 # Some programs on solaris are only found in /usr/xpg4/bin (or work better than others versions).
 AC_PATH_PROG(SHELL_PATH, sh, /bin/sh, [/usr/xpg4/bin$PATH_SEPARATOR$PATH])
 AC_PATH_PROG(FAKEROOT_PATH, fakeroot, /usr/bin/fakeroot, [/usr/xpg4/bin$PATH_SEPARATOR$PATH])
@@ -1203,8 +1196,3 @@ AC_OUTPUT
 AC_MSG_RESULT()
 AC_MSG_RESULT([    rsync ${RSYNC_VERSION} configuration successful])
 AC_MSG_RESULT()
-if test x$HAVE_YODL2MAN != x1; then
-    AC_MSG_RESULT([    Note that yodl2man was not found, so pre-existing manpage files will be])
-    AC_MSG_RESULT([    used w/o change (if available) -- no .yo file changes will be used.])
-    AC_MSG_RESULT()
-fi
diff --git a/maybe-make-man b/maybe-make-man
new file mode 100755
index 00000000..334c3934
--- /dev/null
+++ b/maybe-make-man
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+if [ x"$2" = x ]; then
+    echo "Usage: $0 SRC_DIR NAME.NUM.md" 1>&2
+    exit 1
+fi
+
+srcdir="$1"
+inname="$2"
+
+if [ ! -d "$srcdir" ]; then
+    echo "The specified SRC_DIR is not a directory: $srcdir" 1>&2
+    exit 1
+fi
+
+if [ ! x"$RSYNC_ALWAYS_BUILD" ]; then
+    # We test our smallest manpage just to see if the python setup works.
+    if ! "$srcdir/md2man" --test "$srcdir/rsync-ssl.1.md" >/dev/null 2>&1; then
+	outname=`echo "$inname" | sed 's/\.md$//'`
+	if [ -f "$outname" ]; then
+	    exit 0
+	elif [ -f "$srcdir/$outname" ]; then
+	    echo "Copying $srcdir/$outname"
+	    cp -p "$srcdir/$outname" .
+	    exit 0
+	else
+	    echo "ERROR: $outname cannot be created."
+	    if [ -f "$HOME/build_farm/build_test.fns" ]; then
+		exit 0 # No exit errorno to avoid a build failure in the samba build farm
+	    else
+		exit 1
+	    fi
+	fi
+    fi
+fi
+
+"$srcdir/md2man" "$srcdir/$inname"
diff --git a/md2man b/md2man
new file mode 100755
index 00000000..9b3c7c4b
--- /dev/null
+++ b/md2man
@@ -0,0 +1,314 @@
+#!/usr/bin/python3
+
+# This script takes a manpage written in github-flavored markdown and turns it
+# into a html web page and a nroff man page.  The input file must have the name
+# of the program and the section in the format: NAME.NUM.md. The output files
+# are written into the current directory named NAME.NUM.html and NAME.NUM.  The
+# input format has one extra extension: if a numbered list starts at 0, it is
+# turned into a description list. The dl's dt tag is taken from the contents of
+# the first tag inside the li, which is usually a p tag or a code tag.  The
+# cmarkgfm lib is used to transforms the input file into html. The html.parser
+# is used as a state machine that both tweaks the html and outputs the nroff
+# data based on the html tags.
+#
+# Copyright (C) 2020 Wayne Davison
+#
+# This program is freely redistributable.
+
+import sys, os, re, argparse, time
+from html.parser import HTMLParser
+
+CONSUMES_TXT = set('h1 h2 p li pre'.split())
+
+HTML_START = """\
+<html><head>
+<title>%s</title>
+<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
+<style>
+body {
+  max-width: 40em;
+  margin: auto;
+  font-size: 1.2em;
+  font-family: 'Roboto', sans-serif;
+}
+blockquote pre code {
+  background: #eee;
+}
+dd p:first-of-type {
+  margin-block-start: 0em;
+}
+</style>
+</head><body>
+"""
+
+HTML_END = """\
+<div style="float: right"><p><i>%s</i></p></div>
+</body></html>
+"""
+
+MAN_START = r"""
+.TH "%s" "%s" "%s" "" ""
+""".lstrip()
+
+MAN_END = """\
+"""
+
+NORM_FONT = ('\1', r"\fP")
+BOLD_FONT = ('\2', r"\fB")
+ULIN_FONT = ('\3', r"\fI")
+
+env_subs = { }
+
+def main():
+    mtime = None
+
+    fi = re.match(r'^(?P<fn>(?P<srcdir>.+/)?(?P<name>(?P<prog>[^/]+)\.(?P<sect>\d+))\.md)$', args.mdfile)
+    if not fi:
+        die('Failed to parse NAME.NUM.md out of input file:', args.mdfile)
+    fi = argparse.Namespace(**fi.groupdict())
+    if not fi.srcdir:
+        fi.srcdir = './'
+
+    chk_files = 'latest-year.h Makefile'.split()
+    for fn in chk_files:
+        try:
+            st = os.lstat(fi.srcdir + fn)
+        except:
+            die('Failed to find', fi.srcdir + fn)
+        if not mtime:
+            mtime = st.st_mtime
+
+    with open(fi.srcdir + 'Makefile', 'r', encoding='utf-8') as fh:
+        for line in fh:
+            m = re.match(r'^(\w+)=(.+)', line)
+            if not m:
+                continue
+            var, val = (m[1], m[2])
+            while re.search(r'\$\{', val):
+                val = re.sub(r'\$\{(\w+)\}', lambda m: env_subs[m[1]], val)
+            env_subs[var] = val
+            if var == 'VERSION':
+                break
+
+    MarkdownToManPage(fi, mtime)
+
+
+class MarkdownToManPage(HTMLParser):
+    def __init__(self, fi, mtime):
+        HTMLParser.__init__(self, convert_charrefs=True)
+
+        self.man_fh = self.html_fh = None
+        self.state = argparse.Namespace(
+                list_state = [ ],
+                p_macro = ".P\n",
+                first_li_tag = False,
+                first_dd_tag = False,
+                dt_from = None,
+                in_pre = False,
+                txt = '',
+                )
+
+        self.date = time.strftime('%d %b %Y', time.localtime(mtime))
+
+        with open(fi.fn, 'r', encoding='utf-8') as fh:
+            txt = re.sub(r'@VERSION@', env_subs['VERSION'], fh.read())
+            txt = re.sub(r'@LIBDIR@', env_subs['libdir'], txt)
+            html = cmarkgfm.github_flavored_markdown_to_html(txt)
+            txt = None
+
+        if args.test:
+            self.html_fh = open(os.devnull, 'w', encoding='utf-8')
+            self.man_fh = self.html_fh
+        else:
+            self.html_fn = fi.name + '.html'
+            self.html_fh = open(self.html_fn, 'w', encoding='utf-8')
+            self.html_fh.write(HTML_START % fi.prog + '(' + fi.sect + ') man page')
+
+            self.man_fn = fi.name
+            self.man_fh = open(self.man_fn, 'w', encoding='utf-8')
+            self.man_fh.write(MAN_START % (fi.prog, fi.sect, self.date))
+
+        self.feed(html)
+
+    def __del__(self):
+        if args.test:
+            print("The test was successful.")
+            return
+
+        if self.html_fh:
+            self.html_fh.write(HTML_END % self.date)
+            self.html_fh.close()
+            print("Output HTML page: ", self.html_fn)
+
+        if self.man_fh:
+            self.man_fh.write(MAN_END)
+            self.man_fh.close()
+            print("Output man page:  ", self.man_fn)
+
+    def handle_starttag(self, tag, attrs_list):
+        st = self.state
+        if args.debug:
+            print('START', tag, attrs_list, st)
+        if st.first_li_tag:
+            if st.list_state[-1] == 'dl':
+                st.dt_from = tag
+                if tag == 'p':
+                    tag = 'dt'
+                else:
+                    self.html_fh.write('<dt>')
+            st.first_li_tag = False
+        if tag == 'p':
+            if not st.first_dd_tag:
+                self.man_fh.write(st.p_macro)
+        elif tag == 'li':
+            st.first_li_tag = True
+            lstate = st.list_state[-1]
+            if lstate == 'dl':
+                return
+            if lstate == 'o':
+                self.man_fh.write(".IP o\n")
+            else:
+                self.man_fh.write(".IP " + str(lstate) + ".\n")
+                st.list_state[-1] += 1
+        elif tag == 'blockquote':
+            self.man_fh.write(".RS 4\n")
+        elif tag == 'pre':
+            st.in_pre = True
+            self.man_fh.write(st.p_macro + ".nf\n")
+        elif tag == 'code' and not st.in_pre:
+            st.txt += BOLD_FONT[0]
+        elif tag == 'strong' or tag == 'bold':
+            st.txt += BOLD_FONT[0]
+        elif tag == 'i' or tag == 'em':
+            st.txt += ULIN_FONT[0]
+        elif tag == 'ol':
+            start = 1
+            for var, val in attrs_list:
+                if var == 'start':
+                    start = int(val) # We only support integers.
+                    break
+            if st.list_state:
+                self.man_fh.write(".RS\n")
+            if start == 0:
+                tag = 'dl'
+                attrs_list = [ ]
+                st.list_state.append('dl')
+            else:
+                st.list_state.append(start)
+            self.man_fh.write(st.p_macro)
+            st.p_macro = ".IP\n"
+        elif tag == 'ul':
+            self.man_fh.write(st.p_macro)
+            if st.list_state:
+                self.man_fh.write(".RS\n")
+                st.p_macro = ".IP\n"
+            st.list_state.append('o')
+        outer_tag = '<' + tag
+        for var, val in attrs_list:
+            outer_tag += ' ' + var + '=' + safeText(val) + '"'
+        self.html_fh.write(outer_tag + '>')
+        st.first_dd_tag = False
+
+    def handle_endtag(self, tag):
+        st = self.state
+        if args.debug:
+            print('  END', tag, st)
+        if tag in CONSUMES_TXT or st.dt_from == tag:
+            txt = st.txt.strip()
+            st.txt = ''
+        else:
+            txt = None
+        add_to_txt = None
+        if tag == 'h1':
+            self.man_fh.write(st.p_macro + '.SH "' + manify(txt) + '"\n')
+        elif tag == 'p':
+            if st.dt_from == 'p':
+                tag = 'dt'
+                self.man_fh.write('.IP "' + manify(txt) + '"\n')
+                st.dt_from = None
+            else:
+                self.man_fh.write(manify(txt) + "\n")
+        elif tag == 'li':
+            if st.list_state[-1] == 'dl':
+                if st.first_li_tag:
+                    die("Invalid 0. -> td translation")
+                tag = 'dd'
+            if txt != '':
+                self.man_fh.write(manify(txt) + "\n")
+            st.first_li_tag = False
+        elif tag == 'blockquote':
+            self.man_fh.write(".RE\n")
+        elif tag == 'pre':
+            st.in_pre = False
+            self.man_fh.write(manify(txt) + "\n.fi\n")
+        elif tag == 'code' and not st.in_pre:
+             add_to_txt = NORM_FONT[0]
+        elif tag == 'strong' or tag == 'bold':
+             add_to_txt = NORM_FONT[0]
+        elif tag == 'i' or tag == 'em':
+             add_to_txt = NORM_FONT[0]
+        elif tag == 'ol' or tag == 'ul':
+            if st.list_state.pop() == 'dl':
+                tag = 'dl'
+            if st.list_state:
+                self.man_fh.write(".RE\n")
+            else:
+                st.p_macro = ".P\n"
+            st.first_dd_tag = False
+        self.html_fh.write('</' + tag + '>')
+        if add_to_txt:
+            if txt is None:
+                st.txt += add_to_txt
+            else:
+                txt += add_to_txt
+        if st.dt_from == tag:
+            self.man_fh.write('.IP "' + manify(txt) + '"\n')
+            self.html_fh.write('</dt><dd>')
+            st.first_dd_tag = True
+            st.dt_from = None
+        elif tag == 'dt':
+            self.html_fh.write('<dd>')
+            st.first_dd_tag = True
+
+    def handle_data(self, data):
+        st = self.state
+        if args.debug:
+            print(' DATA', [data], st)
+        self.html_fh.write(safeText(data))
+        st.txt += data
+
+
+def manify(txt):
+    return re.sub(r"^(['.])", r'\&\1', txt.replace('\\', '\\\\')
+            .replace(NORM_FONT[0], NORM_FONT[1])
+            .replace(BOLD_FONT[0], BOLD_FONT[1])
+            .replace(ULIN_FONT[0], ULIN_FONT[1]), flags=re.M)
+
+
+def safeText(txt):
+    return txt.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
+
+
+def warn(*msg):
+    print(*msg, file=sys.stderr)
+
+
+def die(*msg):
+    warn(*msg)
+    sys.exit(1)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Transform a NAME.NUM.md markdown file into a NAME.NUM.html web page & a NAME.NUM man page.', add_help=False)
+    parser.add_argument('--test', action='store_true', help='Test if we can parse the input w/o updating any files.')
+    parser.add_argument('--debug', '-D', action='count', default=0, help='Output copious info on the html parsing.')
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    parser.add_argument('mdfile', help="The NAME.NUM.md file to parse.")
+    args = parser.parse_args()
+
+    try:
+        import cmarkgfm
+    except:
+        die("The cmarkgfm library is not available for python3.")
+
+    main()
diff --git a/packaging/nightly-rsync b/packaging/nightly-rsync
index eb747432..3ec55190 100755
--- a/packaging/nightly-rsync
+++ b/packaging/nightly-rsync
@@ -34,11 +34,12 @@ def main():
         die("$dest does not exist")
     if not os.path.isdir('.git'):
         die("There is no .git dir in the current directory.")
-    if not os.path.exists('rsyncd.conf.yo'):
+    if not os.path.exists('rsyncd.conf.5.md'):
         die("There is no rsync checkout in the current directory.")
 
     if args.make_tar:
         check_git_state('master')
+        cmd_chk(['touch', 'latest-year.h'])
         cmd_chk(['make', gen_target])
 
         extra_files = get_extra_files()
@@ -73,20 +74,13 @@ def main():


-- 
The rsync repository.



More information about the rsync-cvs mailing list