From f0cd5b9c13a412c2248b74da2cef04ec9bcffc10 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 2 Mar 2021 23:36:07 -0500 Subject: [PATCH] Convert command-line options processing to getopt_long. Instead of a hand-written command-line options parser, let's use getopt_long which is hopefully a bit more straightforward to modify. Care has been taken to preserve the current option semantics as closely as possible, including the weird --m4 option behaviour. The Gnulib getopt-gnu module is used for portability. --- .gitignore | 6 + .gitmodules | 3 + Makefile.am | 20 +- bootstrap | 36 ++- build-aux/.gitignore | 9 + build-aux/gitlog-to-changelog | 515 ---------------------------------- common | 2 +- configure.ac | 16 +- gnulib | 1 + m4/.gitignore | 29 +- m4/gnulib-cache.m4 | 65 +++++ src/main.c | 353 +++++++++++------------ src/main.h | 1 + tests/options.at | 14 +- 14 files changed, 362 insertions(+), 708 deletions(-) create mode 100644 build-aux/.gitignore delete mode 100755 build-aux/gitlog-to-changelog create mode 160000 gnulib create mode 100644 m4/gnulib-cache.m4 diff --git a/.gitignore b/.gitignore index 35d44a7..f5280dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ +*.la +*.lo *.o .deps .dirstamp +.libs /INSTALL /aclocal.m4 /atconfig @@ -10,10 +13,13 @@ /config.* /configure /depcomp +/exported.sh /gob2 /gob2.m4 /gob2.spec /install-sh +/lib +/libgnu.a /libtool /ltmain.sh /missing diff --git a/.gitmodules b/.gitmodules index a0019b3..2b898cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "common"] path = common url = https://git.draconx.ca/dxcommon.git +[submodule "gnulib"] + path = gnulib + url = https://git.savannah.gnu.org/r/gnulib.git diff --git a/Makefile.am b/Makefile.am index ae3dae8..f7f54e6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -# Copyright © 2019-2020 Nick Bowler +# Copyright © 2019-2021 Nick Bowler # # Based on original work Copyright © 1999-2013 Jiri (George) Lebl. # @@ -9,26 +9,31 @@ ACLOCAL_AMFLAGS = -I m4 -I common/m4 AM_CPPFLAGS = -I$(top_builddir)/src -I$(top_srcdir)/src \ - -I$(builddir) -I$(srcdir) -DPKGDATADIR=\"$(pkgdatadir)\" + -I$(top_builddir)/lib -I$(top_srcdir)/lib \ + -DPKGDATADIR=\"$(pkgdatadir)\" AM_CFLAGS = $(LIBGLIB_CFLAGS) AM_YFLAGS = -d -t bin_PROGRAMS = gob2 -EXTRA_DIST = COPYING.GPL3 COPYING.generated-code \ +EXTRA_DIST = COPYING.GPL3 COPYING.generated-code common/scripts/fix-gnulib.pl \ examples/GNOME_Foo_SomeInterface.idl examples/README \ examples/foo-some-interface.gob examples/gtk-button-count.gob \ examples/my-glade-main.c examples/my-glade.glade \ examples/my-glade.gob gob2.spec src/generate_treefuncs.pl \ src/lexer.l src/lexer.stamp src/treefuncs.def \ src/treefuncs.stamp t/str.gob t/test-fooable.c t/test-fooable.h \ - t/test.gob + t/test.gob m4/gnulib-cache.m4 bootstrap -CLEANFILES = +CLEANFILES = $(EXTRA_LIBRARIES) DISTCLEANFILES = +MOSTLYCLEANFILES = MAINTAINERCLEANFILES = src/lexer.c src/lexer.h src/lexer.stamp \ src/treefuncs.c src/treefuncs.h src/treefuncs.stamp +# For Gnulib +EXTRA_LIBRARIES = + EXTRA_PROGRAMS = parser-rdeps parser_rdeps_SOURCES = src/main.c src/lexer.c $(parser_rdeps_OBJECTS): src/parse.h @@ -37,8 +42,8 @@ noinst_HEADERS = src/main.h src/treefuncs.h src/out.h src/util.h src/checks.h gob2_SOURCES = src/main.c src/main.h src/treefuncs.c src/out.c src/util.c \ src/checks.c src/parse.y src/lexer.c src/lexer.h -gob2_LDADD = $(LIBGLIB_LIBS) -$(gob2_OBJECTS): src/treefuncs.h +gob2_LDADD = $(LIBGLIB_LIBS) libgnu.a +$(gob2_OBJECTS): src/treefuncs.h $(gnulib_headers) man_MANS = doc/gob2.1 EXTRA_DIST += doc/makehtml.pl @@ -205,4 +210,5 @@ atlocal: config.status check_DATA = atlocal CLEANFILES += atlocal +include $(top_srcdir)/lib/gnulib.mk include $(top_srcdir)/common/snippet/autotest.mk diff --git a/bootstrap b/bootstrap index f9cbd41..b5c5856 100755 --- a/bootstrap +++ b/bootstrap @@ -1,16 +1,46 @@ #!/bin/sh +# +# Copyright © 2011-2012, 2021 Nick Bowler +# +# Simple script to get started from a fresh git checkout. +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. -argv0=$0 +scriptname=$0 -err () { printf '%s: %s\n' "$argv0" "$@" 1>&2; } +err () { printf '%s: %s\n' "$scriptname" "$*" 1>&2; } die () { err "$@"; exit 1; } : "${AUTORECONF=autoreconf}" +: "${GNULIB=gnulib}" +: "${PERL=perl}" : "${GIT=git}" $GIT submodule update --init || err "Failed to update submodules from git." +if test -x $GNULIB/gnulib-tool; then + $GNULIB/gnulib-tool --update -S || die "Failed to update gnulib." +else + err "Gnulib sources are not properly installed in $GNULIB/" + cat >&2 <<'EOF' + +To bootstrap this package using an external Gnulib, you can set the GNULIB +environment variable to indicate the location of the Gnulib sources. +EOF + test ! -f configure || cat >&2 <<'EOF' + +However, it seems this package is already bootstrapped. It should not +normally be necessary to run this script from a release tarball. +EOF + exit 1 +fi + +$PERL common/scripts/fix-gnulib.pl -o lib/gnulib.mk -i lib/gnulib.mk.in || + die "Failed to fixup Gnulib makefile fragment." + # Punt some automake-generated files so that Gentoo's wrapper script -# doesn'# try to detect the automake version in use. +# doesn't try to detect the automake version in use. rm -f Makefile.in aclocal.m4 $AUTORECONF -fis diff --git a/build-aux/.gitignore b/build-aux/.gitignore new file mode 100644 index 0000000..ea455e2 --- /dev/null +++ b/build-aux/.gitignore @@ -0,0 +1,9 @@ +compile +config.guess +config.sub +depcomp +gitlog-to-changelog +install-sh +ltmain.sh +missing +ylwrap diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog deleted file mode 100755 index 5112767..0000000 --- a/build-aux/gitlog-to-changelog +++ /dev/null @@ -1,515 +0,0 @@ -#!/bin/sh -#! -*-perl-*- - -# Convert git log output to ChangeLog format. - -# Copyright (C) 2008-2020 Free Software Foundation, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Written by Jim Meyering - -# This is a prologue that allows to run a perl script as an executable -# on systems that are compliant to a POSIX version before POSIX:2017. -# On such systems, the usual invocation of an executable through execlp() -# or execvp() fails with ENOEXEC if it is a script that does not start -# with a #! line. The script interpreter mentioned in the #! line has -# to be /bin/sh, because on GuixSD systems that is the only program that -# has a fixed file name. The second line is essential for perl and is -# also useful for editing this file in Emacs. The next two lines below -# are valid code in both sh and perl. When executed by sh, they re-execute -# the script through the perl program found in $PATH. The '-x' option -# is essential as well; without it, perl would re-execute the script -# through /bin/sh. When executed by perl, the next two lines are a no-op. -eval 'exec perl -wSx "$0" "$@"' - if 0; - -my $VERSION = '2018-03-07 03:47'; # UTC -# The definition above must lie within the first 8 lines in order -# for the Emacs time-stamp write hook (at end) to update it. -# If you change this file with Emacs, please let the write hook -# do its job. Otherwise, update this string manually. - -use strict; -use warnings; -use Getopt::Long; -use POSIX qw(strftime); - -(my $ME = $0) =~ s|.*/||; - -# use File::Coda; # https://meyering.net/code/Coda/ -END { - defined fileno STDOUT or return; - close STDOUT and return; - warn "$ME: failed to close standard output: $!\n"; - $? ||= 1; -} - -sub usage ($) -{ - my ($exit_code) = @_; - my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); - if ($exit_code != 0) - { - print $STREAM "Try '$ME --help' for more information.\n"; - } - else - { - print $STREAM < ChangeLog - $ME -- -n 5 foo > last-5-commits-to-branch-foo - -SPECIAL SYNTAX: - -The following types of strings are interpreted specially when they appear -at the beginning of a log message line. They are not copied to the output. - - Copyright-paperwork-exempt: Yes - Append the "(tiny change)" notation to the usual "date name email" - ChangeLog header to mark a change that does not require a copyright - assignment. - Co-authored-by: Joe User - List the specified name and email address on a second - ChangeLog header, denoting a co-author. - Signed-off-by: Joe User - These lines are simply elided. - -In a FILE specified via --amend, comment lines (starting with "#") are ignored. -FILE must consist of pairs where SHA is a 40-byte SHA1 (alone on -a line) referring to a commit in the current project, and CODE refers to one -or more consecutive lines of Perl code. Pairs must be separated by one or -more blank line. - -Here is sample input for use with --amend=FILE, from coreutils: - -3a169f4c5d9159283548178668d2fae6fced3030 -# fix typo in title: -s/all tile types/all file types/ - -1379ed974f1fa39b12e2ffab18b3f7a607082202 -# Due to a bug in vc-dwim, I mis-attributed a patch by Paul to myself. -# Change the author to be Paul. Note the escaped "@": -s,Jim .*>,Paul Eggert , - -EOF - } - exit $exit_code; -} - -# If the string $S is a well-behaved file name, simply return it. -# If it contains white space, quotes, etc., quote it, and return the new string. -sub shell_quote($) -{ - my ($s) = @_; - if ($s =~ m![^\w+/.,-]!) - { - # Convert each single quote to '\'' - $s =~ s/\'/\'\\\'\'/g; - # Then single quote the string. - $s = "'$s'"; - } - return $s; -} - -sub quoted_cmd(@) -{ - return join (' ', map {shell_quote $_} @_); -} - -# Parse file F. -# Comment lines (starting with "#") are ignored. -# F must consist of pairs where SHA is a 40-byte SHA1 -# (alone on a line) referring to a commit in the current project, and -# CODE refers to one or more consecutive lines of Perl code. -# Pairs must be separated by one or more blank line. -sub parse_amend_file($) -{ - my ($f) = @_; - - open F, '<', $f - or die "$ME: $f: failed to open for reading: $!\n"; - - my $fail; - my $h = {}; - my $in_code = 0; - my $sha; - while (defined (my $line = )) - { - $line =~ /^\#/ - and next; - chomp $line; - $line eq '' - and $in_code = 0, next; - - if (!$in_code) - { - $line =~ /^([[:xdigit:]]{40})$/ - or (warn "$ME: $f:$.: invalid line; expected an SHA1\n"), - $fail = 1, next; - $sha = lc $1; - $in_code = 1; - exists $h->{$sha} - and (warn "$ME: $f:$.: duplicate SHA1\n"), - $fail = 1, next; - } - else - { - $h->{$sha} ||= ''; - $h->{$sha} .= "$line\n"; - } - } - close F; - - $fail - and exit 1; - - return $h; -} - -# git_dir_option $SRCDIR -# -# From $SRCDIR, the --git-dir option to pass to git (none if $SRCDIR -# is undef). Return as a list (0 or 1 element). -sub git_dir_option($) -{ - my ($srcdir) = @_; - my @res = (); - if (defined $srcdir) - { - my $qdir = shell_quote $srcdir; - my $cmd = "cd $qdir && git rev-parse --show-toplevel"; - my $qcmd = shell_quote $cmd; - my $git_dir = qx($cmd); - defined $git_dir - or die "$ME: cannot run $qcmd: $!\n"; - $? == 0 - or die "$ME: $qcmd had unexpected exit code or signal ($?)\n"; - chomp $git_dir; - push @res, "--git-dir=$git_dir/.git"; - } - @res; -} - -{ - my $since_date; - my $until_date; - my $format_string = '%s%n%b%n'; - my $amend_file; - my $append_dot = 0; - my $cluster = 1; - my $ignore_matching; - my $ignore_line; - my $strip_tab = 0; - my $strip_cherry_pick = 0; - my $srcdir; - GetOptions - ( - help => sub { usage 0 }, - version => sub { print "$ME version $VERSION\n"; exit }, - 'since=s' => \$since_date, - 'until=s' => \$until_date, - 'format=s' => \$format_string, - 'amend=s' => \$amend_file, - 'append-dot' => \$append_dot, - 'cluster!' => \$cluster, - 'ignore-matching=s' => \$ignore_matching, - 'ignore-line=s' => \$ignore_line, - 'strip-tab' => \$strip_tab, - 'strip-cherry-pick' => \$strip_cherry_pick, - 'srcdir=s' => \$srcdir, - ) or usage 1; - - defined $since_date - and unshift @ARGV, "--since=$since_date"; - defined $until_date - and unshift @ARGV, "--until=$until_date"; - - # This is a hash that maps an SHA1 to perl code (i.e., s/old/new/) - # that makes a correction in the log or attribution of that commit. - my $amend_code = defined $amend_file ? parse_amend_file $amend_file : {}; - - my @cmd = ('git', - git_dir_option $srcdir, - qw(log --log-size), - '--pretty=format:%H:%ct %an <%ae>%n%n'.$format_string, @ARGV); - open PIPE, '-|', @cmd - or die ("$ME: failed to run '". quoted_cmd (@cmd) ."': $!\n" - . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); - - my $prev_multi_paragraph; - my $prev_date_line = ''; - my @prev_coauthors = (); - my @skipshas = (); - while (1) - { - defined (my $in = ) - or last; - $in =~ /^log size (\d+)$/ - or die "$ME:$.: Invalid line (expected log size):\n$in"; - my $log_nbytes = $1; - - my $log; - my $n_read = read PIPE, $log, $log_nbytes; - $n_read == $log_nbytes - or die "$ME:$.: unexpected EOF\n"; - - # Extract leading hash. - my ($sha, $rest) = split ':', $log, 2; - defined $sha - or die "$ME:$.: malformed log entry\n"; - $sha =~ /^[[:xdigit:]]{40}$/ - or die "$ME:$.: invalid SHA1: $sha\n"; - - my $skipflag = 0; - if (@skipshas) - { - foreach(@skipshas) - { - if ($sha =~ /^$_/) - { - $skipflag = $_; - last; - } - } - } - - # If this commit's log requires any transformation, do it now. - my $code = $amend_code->{$sha}; - if (defined $code) - { - eval 'use Safe'; - my $s = new Safe; - # Put the unpreprocessed entry into "$_". - $_ = $rest; - - # Let $code operate on it, safely. - my $r = $s->reval("$code") - or die "$ME:$.:$sha: failed to eval \"$code\":\n$@\n"; - - # Note that we've used this entry. - delete $amend_code->{$sha}; - - # Update $rest upon success. - $rest = $_; - } - - # Remove lines inserted by "git cherry-pick". - if ($strip_cherry_pick) - { - $rest =~ s/^\s*Conflicts:\n.*//sm; - $rest =~ s/^\s*\(cherry picked from commit [\da-f]+\)\n//m; - } - - my @line = split /[ \t]*\n/, $rest; - my $author_line = shift @line; - defined $author_line - or die "$ME:$.: unexpected EOF\n"; - $author_line =~ /^(\d+) (.*>)$/ - or die "$ME:$.: Invalid line " - . "(expected date/author/email):\n$author_line\n"; - - # Format 'Copyright-paperwork-exempt: Yes' as a standard ChangeLog - # `(tiny change)' annotation. - my $tiny = (grep (/^(?:Copyright-paperwork-exempt|Tiny-change):\s+[Yy]es$/, @line) - ? ' (tiny change)' : ''); - - my $date_line = sprintf "%s %s$tiny\n", - strftime ("%Y-%m-%d", localtime ($1)), $2; - - my @coauthors = grep /^Co-authored-by:.*$/, @line; - # Omit meta-data lines we've already interpreted. - @line = grep !/^(?:Signed-off-by:[ ].*>$ - |Co-authored-by:[ ] - |Copyright-paperwork-exempt:[ ] - |Tiny-change:[ ] - )/x, @line; - - # Remove leading and trailing blank lines. - if (@line) - { - while ($line[0] =~ /^\s*$/) { shift @line; } - while ($line[$#line] =~ /^\s*$/) { pop @line; } - } - - # Handle Emacs gitmerge.el "skipped" commits. - # Yes, this should be controlled by an option. So sue me. - if ( grep /^(; )?Merge from /, @line ) - { - my $found = 0; - foreach (@line) - { - if (grep /^The following commit.*skipped:$/, $_) - { - $found = 1; - ## Reset at each merge to reduce chance of false matches. - @skipshas = (); - next; - } - if ($found && $_ =~ /^([[:xdigit:]]{7,}) [^ ]/) - { - push ( @skipshas, $1 ); - } - } - } - - # Ignore commits that match the --ignore-matching pattern, if specified. - if (defined $ignore_matching && @line && $line[0] =~ /$ignore_matching/) - { - $skipflag = 1; - } - elsif ($skipflag) - { - ## Perhaps only warn if a pattern matches more than once? - warn "$ME: warning: skipping $sha due to $skipflag\n"; - } - - if (! $skipflag) - { - if (defined $ignore_line && @line) - { - @line = grep ! /$ignore_line/, @line; - while ($line[$#line] =~ /^\s*$/) { pop @line; } - } - - # Record whether there are two or more paragraphs. - my $multi_paragraph = grep /^\s*$/, @line; - - # Format 'Co-authored-by: A U Thor ' lines in - # standard multi-author ChangeLog format. - for (@coauthors) - { - s/^Co-authored-by:\s*/\t /; - s/\s*/ - or warn "$ME: warning: missing email address for " - . substr ($_, 5) . "\n"; - } - - # If clustering of commit messages has been disabled, if this header - # would be different from the previous date/name/etc. header, - # or if this or the previous entry consists of two or more paragraphs, - # then print the header. - if ( ! $cluster - || $date_line ne $prev_date_line - || "@coauthors" ne "@prev_coauthors" - || $multi_paragraph - || $prev_multi_paragraph) - { - $prev_date_line eq '' - or print "\n"; - print $date_line; - @coauthors - and print join ("\n", @coauthors), "\n"; - } - $prev_date_line = $date_line; - @prev_coauthors = @coauthors; - $prev_multi_paragraph = $multi_paragraph; - - # If there were any lines - if (@line == 0) - { - warn "$ME: warning: empty commit message:\n $date_line\n"; - } - else - { - if ($append_dot) - { - # If the first line of the message has enough room, then - if (length $line[0] < 72) - { - # append a dot if there is no other punctuation or blank - # at the end. - $line[0] =~ /[[:punct:]\s]$/ - or $line[0] .= '.'; - } - } - - # Remove one additional leading TAB from each line. - $strip_tab - and map { s/^\t// } @line; - - # Prefix each non-empty line with a TAB. - @line = map { length $_ ? "\t$_" : '' } @line; - - print "\n", join ("\n", @line), "\n"; - } - } - - defined ($in = ) - or last; - $in ne "\n" - and die "$ME:$.: unexpected line:\n$in"; - } - - close PIPE - or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; - # FIXME-someday: include $PROCESS_STATUS in the diagnostic - - # Complain about any unused entry in the --amend=F specified file. - my $fail = 0; - foreach my $sha (keys %$amend_code) - { - warn "$ME:$amend_file: unused entry: $sha\n"; - $fail = 1; - } - - exit $fail; -} - -# Local Variables: -# mode: perl -# indent-tabs-mode: nil -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-line-limit: 50 -# time-stamp-start: "my $VERSION = '" -# time-stamp-format: "%:y-%02m-%02d %02H:%02M" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "'; # UTC" -# End: diff --git a/common b/common index 4f3e19a..1688bad 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 4f3e19a6298267a8675de26e3e3e16afa97cf6a1 +Subproject commit 1688bad1e5dc89cacf33bc426c92a4abf2bc0647 diff --git a/configure.ac b/configure.ac index 0fd20f7..89d0d85 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -dnl Copyright © 2019-2020 Nick Bowler +dnl Copyright © 2019-2021 Nick Bowler dnl dnl Based on original work Copyright © 1999-2013 Jiri (George) Lebl. dnl @@ -8,22 +8,30 @@ dnl There is NO WARRANTY, to the extent permitted by law. AC_INIT([GObject Builder], [2.0.20a], [nbowler@draconx.ca], [gob-dx]) AC_CONFIG_SRCDIR([src/treefuncs.def]) +AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) -AM_INIT_AUTOMAKE([-Wall subdir-objects dist-xz]) +AM_INIT_AUTOMAKE([-Wall -Wno-portability subdir-objects dist-xz]) AM_SILENT_RULES([yes]) DX_AUTOMAKE_COMPAT AC_PROG_CC +gl_EARLY + AC_PROG_CXX AC_PROG_YACC +LT_INIT +gl_INIT + +AC_CACHE_SAVE + +m4_include([lib/gnulib.mk]) + AC_ARG_VAR([PERL], [command to execute perl programs]) AC_CHECK_PROGS([PERL], [perl], [false]) AM_CONDITIONAL([HAVE_PERL], [$PERL -e 'my $x = 42; exit $x'; test $? = 42]) -LT_INIT - DX_PROG_FLEX([], [have_flex=yes], [have_flex=no]) AM_CONDITIONAL([HAVE_FLEX], [test x"$have_flex" = x"yes"]) diff --git a/gnulib b/gnulib new file mode 160000 index 0000000..3b0808a --- /dev/null +++ b/gnulib @@ -0,0 +1 @@ +Subproject commit 3b0808afa4236e6fa221eac2ec25dc2a8ef09557 diff --git a/m4/.gitignore b/m4/.gitignore index 38066dd..9e206c5 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -1,5 +1,24 @@ -libtool.m4 -ltoptions.m4 -ltsugar.m4 -ltversion.m4 -lt~obsolete.m4 +/00gnulib.m4 +/absolute-header.m4 +/extensions.m4 +/extern-inline.m4 +/getopt.m4 +/gnulib-common.m4 +/gnulib-comp.m4 +/gnulib-tool.m4 +/include_next.m4 +/libtool.m4 +/ltoptions.m4 +/ltsugar.m4 +/ltversion.m4 +/lt~obsolete.m4 +/nocrash.m4 +/off_t.m4 +/pid_t.m4 +/ssize_t.m4 +/stddef_h.m4 +/sys_types_h.m4 +/unistd_h.m4 +/warn-on-use.m4 +/wchar_t.m4 +/zzgnulib.m4 diff --git a/m4/gnulib-cache.m4 b/m4/gnulib-cache.m4 new file mode 100644 index 0000000..624dee6 --- /dev/null +++ b/m4/gnulib-cache.m4 @@ -0,0 +1,65 @@ +# Copyright (C) 2002-2021 Free Software Foundation, Inc. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this file. If not, see . +# +# As a special exception to the GNU General Public License, +# this file may be distributed as part of a program that +# contains a configuration script generated by Autoconf, under +# the same distribution terms as the rest of that program. +# +# Generated by gnulib-tool. +# +# This file represents the specification of how gnulib-tool is used. +# It acts as a cache: It is written and read by gnulib-tool. +# In projects that use version control, this file is meant to be put under +# version control, like the configure.ac and various Makefile.am files. + + +# Specification in the form of a command-line invocation: +# gnulib-tool --import \ +# --lib=libgnu \ +# --source-base=lib \ +# --m4-base=m4 \ +# --doc-base=doc \ +# --tests-base=tests \ +# --aux-dir=build-aux \ +# --lgpl=3orGPLv2 \ +# --makefile-name=gnulib.mk.in \ +# --conditional-dependencies \ +# --no-libtool \ +# --macro-prefix=gl \ +# --no-vc-files \ +# getopt-gnu \ +# gitlog-to-changelog + +# Specification in the form of a few gnulib-tool.m4 macro invocations: +gl_LOCAL_DIR([]) +gl_MODULES([ + getopt-gnu + gitlog-to-changelog +]) +gl_AVOID([]) +gl_SOURCE_BASE([lib]) +gl_M4_BASE([m4]) +gl_PO_BASE([]) +gl_DOC_BASE([doc]) +gl_TESTS_BASE([tests]) +gl_LIB([libgnu]) +gl_LGPL([3orGPLv2]) +gl_MAKEFILE_NAME([gnulib.mk.in]) +gl_CONDITIONAL_DEPENDENCIES +gl_MACRO_PREFIX([gl]) +gl_PO_DOMAIN([]) +gl_WITNESS_C_MACRO([]) +gl_VC_FILES([false]) diff --git a/src/main.c b/src/main.c index 295199c..c994d5a 100644 --- a/src/main.c +++ b/src/main.c @@ -2,7 +2,7 @@ * Copyright (C) 1999,2000 the Free Software Foundation. * Copyright (C) 2000 Eazel, Inc. * Copyright (C) 2001-2011 George (Jiri) Lebl - * Copyright © 2019-2020 Nick Bowler + * Copyright © 2019-2021 Nick Bowler * * Author: George (Jiri) Lebl * @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include "treefuncs.h" #include "parse.h" @@ -40,6 +42,44 @@ #include "main.h" +enum { + SOPT_END = UCHAR_MAX, + LOPT_VERSION, + LOPT_NO_TOUCH, + LOPT_FILE_SEP, + LOPT_M4, + LOPT_M4_CLEAN, + LOPT_M4_DIR +}; + +static const char sopts[] = "wnho:"; +static const struct option lopts[] = { + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, LOPT_VERSION }, + { "exit-on-warn", 0, NULL, 'w' }, + { "no-exit-on-warn", 0, &exit_on_warn, FALSE }, + { "for-cpp", 0, &for_cpp, TRUE }, + { "no-extern-c", 0, &no_extern_c, TRUE }, + { "no-gnu", 0, &no_gnu, TRUE }, + { "no-touch", 0, NULL, LOPT_NO_TOUCH }, + { "no-touch-headers", 0, &no_touch_headers, TRUE }, + { "always-private-header", 0, &private_header, PRIVATE_HEADER_ALWAYS }, + { "ondemand-private-header", 0, &private_header, PRIVATE_HEADER_ONDEMAND }, + { "no-private-header", 0, &private_header, PRIVATE_HEADER_NEVER }, + { "always-private-struct", 0, &always_private_struct, TRUE }, + { "m4", 0, NULL, LOPT_M4 }, + { "m4-clean", 0, NULL, LOPT_M4_CLEAN }, + { "m4-dir", 0, NULL, LOPT_M4_DIR }, + { "no-write", 0, NULL, 'n' }, + { "no-lines", 0, &no_lines, TRUE }, + { "no-self-alias", 0, &no_self_alias, TRUE }, + { "no-kill-underscores", 0, NULL, 0 /* no-op */ }, + { "output-dir", 1, NULL, 'o' }, + { "file-sep", 2, NULL, LOPT_FILE_SEP }, + { "gtk3", 0, >k3_ok, TRUE }, + { 0 } +}; + char *filename = NULL; int yyparse(void); @@ -128,7 +168,6 @@ gint prealloc = 0; gboolean use_m4 = FALSE; /* preprocess sources with m4 */ -gboolean use_m4_clean = FALSE; /* preprocess sources with m4, no m4 flags */ char *m4_commandline = NULL; #define M4_INCLUDE_DIR PKGDATADIR "/m4" #define M4_BASE_FILENAME "gobm4.m4" @@ -4603,184 +4642,146 @@ print_help(void) puts("End world hunger, donate to the World Food Programme: https://www.wfp.org/"); } -static void -parse_options(int argc, char *argv[]) +/* + * Called after getopt_long receives an --m4 argument. Immediately stop + * processing options. Then all non-option arguments seen so far together + * with all remaining arguments are appended to M4_COMMANDLINE. If m4_clean + * is false, then M4_FLAGS is inserted before the first non-option argument, + * if any. + * + * The resulting string is returned, which should be freed by the caller. + */ +static char *parse_m4_options(int argc, char **argv, gboolean m4_clean) { - int i; - int got_file = FALSE; - int no_opts = FALSE; - int m4_opts = FALSE; /* if we are just passing on args to m4 */ - - filename = NULL; - - for(i = 1 ; i < argc; i++) { - if(m4_opts) { - char *new_commandline; - g_assert(m4_commandline!=NULL); - - /* check whether this one looks like the filename */ - if((!strcmp(argv[i],"-") || argv[i][0] != '-') - && !got_file) { - const gchar *m4_flags=use_m4_clean?"":M4_FLAGS; - filename = argv[i]; - got_file = TRUE; - - /* insert flags before the filename */ - new_commandline=g_strconcat(m4_commandline, - " ", - m4_flags, - " ", - argv[i], - NULL); - } + char **nonopt = NULL, *save_argv0, *ret; + int opt; - /* just an ordinary option */ - else - new_commandline=g_strconcat(m4_commandline, - " ", - argv[i], - NULL); - - /* free old commandline */ - g_free(m4_commandline); - m4_commandline=new_commandline; - - } else if(no_opts || - argv[i][0] != '-') { - /*must be a file*/ - if(got_file) { - fprintf(stderr, "Specify only one file!\n"); - print_usage(stderr); - exit(1); - } - filename = argv[i]; - got_file = TRUE; - } else if(strcmp(argv[i], "--help")==0) { - print_help(); - exit(0); - } else if(strcmp(argv[i], "--version")==0) { - print_version(); - exit(0); - } else if(strcmp(argv[i], "--exit-on-warn")==0) { - exit_on_warn = TRUE; - } else if(strcmp(argv[i], "--no-exit-on-warn")==0) { - exit_on_warn = FALSE; - } else if(strcmp(argv[i], "--for-cpp")==0) { - for_cpp = TRUE; - } else if(strcmp(argv[i], "--no-touch")==0) { - no_touch = TRUE; - no_touch_headers = TRUE; - } else if(strcmp(argv[i], "--no-touch-headers")==0) { - no_touch_headers = TRUE; - } else if(strcmp(argv[i], "--ondemand-private-header")==0) { - private_header = PRIVATE_HEADER_ONDEMAND; - } else if(strcmp(argv[i], "--always-private-header")==0) { - private_header = PRIVATE_HEADER_ALWAYS; - } else if(strcmp(argv[i], "--no-private-header")==0) { - private_header = PRIVATE_HEADER_NEVER; - } else if(strcmp(argv[i], "--no-gnu")==0) { - no_gnu = TRUE; - } else if(strcmp(argv[i], "--no-extern-c")==0) { - no_extern_c = TRUE; - } else if(strcmp(argv[i], "--no-write")==0) { + /* First, conclude getopt run and reset with remaining args */ + getopt_long(optind, argv, sopts, lopts, NULL); + argv += optind-1; + argc -= optind-1; + optind = 0; + + save_argv0 = argv[0]; + argv[0] = M4_COMMANDLINE; + + if (m4_clean) { + ret = g_strjoinv(" ", argv); + argv[0] = save_argv0; + return ret; + } + + /* Locate first non-option argument, if any. */ + while ((opt = getopt_long(argc, argv, "-", NULL, NULL)) != -1) { + if (opt == 1) { + nonopt = &argv[optind-2]; + break; + } + } + + /* If there is a non-option but the above didn't see it, must be "--" */ + if (!nonopt && argv[optind]) + nonopt = &argv[optind-2]; + + if (nonopt) { + /* Found non-option, insert M4_FLAGS just before it. */ + char *save_argv[3] = { nonopt[0], nonopt[1], nonopt[2] }; + + nonopt[1] = M4_FLAGS; + nonopt[2] = NULL; + nonopt[0] = g_strjoinv(" ", argv); + + nonopt[1] = save_argv[1]; + nonopt[2] = save_argv[2]; + ret = g_strjoinv(" ", nonopt); + + g_free(nonopt[0]); + nonopt[0] = save_argv[0]; + } else { + /* Only options, not inserting M4_FLAGS. */ + ret = g_strjoinv(" ", argv); + } + + argv[0] = save_argv0; + return ret; +} + +static int parse_options(int argc, char **argv) +{ + gboolean show_m4_dir = FALSE, m4_clean = FALSE; + char *raw_file_sep = "-"; + int opt; + + opterr = 0; + while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { + switch (opt) { + case 'n': no_write = TRUE; - } else if(strcmp(argv[i], "--no-lines")==0) { - no_lines = TRUE; - } else if(strcmp(argv[i], "--no-self-alias")==0) { - no_self_alias = TRUE; - } else if(strcmp(argv[i], "--no-kill-underscores")==0) { - /* no op */; - } else if(strcmp(argv[i], "--always-private-struct")==0) { - always_private_struct = TRUE; - } else if(strcmp(argv[i], "--m4-dir")==0) { - printf("%s\n",M4_INCLUDE_DIR); - exit(0); - } else if(strcmp(argv[i], "--m4")==0) { - use_m4 = TRUE; - use_m4_clean=FALSE; - m4_opts = TRUE; - m4_commandline=g_strdup(M4_COMMANDLINE); - } else if(strcmp(argv[i], "--m4-clean")==0) { + break; + case 'o': + output_dir = optarg; + break; + case 'w': + exit_on_warn = TRUE; + break; + case LOPT_FILE_SEP: + raw_file_sep = optarg ? optarg : ""; + break; + case LOPT_M4_DIR: + show_m4_dir = TRUE; + break; + case LOPT_NO_TOUCH: + no_touch = no_touch_headers = TRUE; + break; + case LOPT_M4_CLEAN: + m4_clean = TRUE; + case LOPT_M4: use_m4 = TRUE; - use_m4_clean=TRUE; - m4_opts = TRUE; - m4_commandline=g_strdup(M4_COMMANDLINE); - } else if (strcmp (argv[i], "-o") == 0 || - strcmp (argv[i], "--output-dir") == 0) { - if (i+1 < argc) { - output_dir = g_strdup (argv[i+1]); - i++; - } else { - output_dir = NULL; - } - } else if (strncmp (argv[i], "-o=", strlen ("-o=")) == 0 || - strncmp (argv[i], - "--output-dir=", - strlen ("--output-dir=")) == 0) { - char *p = strchr (argv[i], '='); - g_assert (p != NULL); - output_dir = g_strdup (p+1); - } else if (strncmp (argv[i], "--file-sep=", - strlen ("--file-sep=")) == 0) { - char *p = strchr (argv[i], '='); - g_assert (p != NULL); - file_sep = *(p+1); - } else if (strncmp (argv[i], "--file-sep", - strlen ("--file-sep")) == 0) { - if (i+1 < argc) { - file_sep = (argv[i+1])[0]; - i++; - } else { - file_sep = 0; - } - } else if(strcmp(argv[i], "--gtk3")==0) { - gtk3_ok = TRUE; - } else if(strcmp(argv[i], "--")==0) { - /*further arguments are files*/ - no_opts = TRUE; - } else if(strncmp(argv[i], "--", 2)==0) { - /*unknown long option*/ - fprintf(stderr, "Unknown option '%s'!\n", argv[i]); - print_usage(stderr); - exit(1); - } else { - /*by now we know we have a string starting with - - which is a short option string*/ - char *p; - for(p = argv[i] + 1; *p; p++) { - switch(*p) { - case 'w': - exit_on_warn=TRUE; - break; - case 'n': - no_write = TRUE; - break; - case 'h': - case '?': - print_help(); - exit(0); - default: - fprintf(stderr, - "Unknown option '%c'!\n", *p); - print_usage(stderr); - exit(1); - } + m4_commandline = parse_m4_options(argc, argv, m4_clean); + goto out; + case LOPT_VERSION: + print_version(); + return 1; + default: + if (optopt == '?') { + case 'h': + print_help(); + return 1; } + + /* Rewind getopt to get internal error messages. */ + optind = 0, opterr = 1; + while (getopt_long(argc, argv, sopts, lopts, NULL) + != opt); + return -1; + case 0: /* no-op or option set by flag */; } } -#if 0 - /* if we are using m4, and got no filename, append m4 flags now */ - if(!got_file && use_m4 && !use_m4_clean) { - char *new_commandline; - new_commandline=g_strconcat(m4_commandline, - " ", - M4_FLAGS, - NULL); - g_free(m4_commandline); - m4_commandline=new_commandline; + filename = argv[optind]; + if (argc > optind+1) { + char *s = g_strjoinv(" ", argv+optind+1); + fprintf(stderr, "%s: Warning: excess arguments ignored: %s\n", + g_get_prgname(), s); + g_free(s); + if (exit_on_warn) + return -1; + } +out: + file_sep = raw_file_sep[0]; + if (raw_file_sep[0] && raw_file_sep[1]) { + fprintf(stderr, "%s: Warning: --file-sep characters beyond the first are ignored\n", + g_get_prgname()); + if (exit_on_warn) + return -1; } -#endif + + if (show_m4_dir) { + printf("%s\n", M4_INCLUDE_DIR); + return 1; + } + + return 0; } static void @@ -4861,9 +4862,17 @@ compare_and_move (const char *old_filename) int main(int argc, char *argv[]) { + int rc; + g_set_prgname(argc > 0 ? argv[0] : "gob2"); - parse_options(argc, argv); + rc = parse_options(argc, argv); + if (rc < 0) { + print_usage(stderr); + return EXIT_FAILURE; + } else if (rc > 0) { + return EXIT_SUCCESS; + } if(use_m4) { yyin = popen(m4_commandline, "r"); diff --git a/src/main.h b/src/main.h index f644df9..6c90250 100644 --- a/src/main.h +++ b/src/main.h @@ -42,6 +42,7 @@ extern gboolean no_lines; extern gboolean no_self_alias; extern gboolean no_kill_underscores; extern gboolean always_private_struct; +extern gboolean gtk3_ok; extern gint prealloc; extern char *filename; diff --git a/tests/options.at b/tests/options.at index 53eda46..f8d56e4 100644 --- a/tests/options.at +++ b/tests/options.at @@ -1,4 +1,4 @@ -dnl Copyright © 2020 Nick Bowler +dnl Copyright © 2020-2021 Nick Bowler dnl License GPLv2+: GNU General Public License version 2 or any later version. dnl This is free software: you are free to change and redistribute it. dnl There is NO WARRANTY, to the extent permitted by law. @@ -569,6 +569,18 @@ AT_CHECK([gob2 --m4 filename --help I am trapped in a test case factory --], [0], [], [experr]) AT_CHECK([test -f m4-test.c]) +set x $gob_m4_args; shift +for arg +do + shift + AS_CASE([$arg], [filename], [], [*], [set x "$@" "$arg"; shift]) +done + +cat >experr <