Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mail.netbsd.org", Issuer "Postmaster NetBSD.org" (verified OK)) by mollari.NetBSD.org (Postfix) with ESMTPS id D5E707A2A7 for ; Sun, 1 Jan 2017 15:15:52 +0000 (UTC) Received: by mail.netbsd.org (Postfix, from userid 605) id 421B18572A; Sun, 1 Jan 2017 15:15:52 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by mail.netbsd.org (Postfix) with ESMTP id C48EB85710 for ; Sun, 1 Jan 2017 15:15:51 +0000 (UTC) X-Virus-Scanned: amavisd-new at netbsd.org Received: from mail.netbsd.org ([IPv6:::1]) by localhost (mail.netbsd.org [IPv6:::1]) (amavisd-new, port 10025) with ESMTP id 9zCa0_adLttM for ; Sun, 1 Jan 2017 15:15:48 +0000 (UTC) Received: from cvs.NetBSD.org (ivanova.netbsd.org [199.233.217.197]) by mail.netbsd.org (Postfix) with ESMTP id 4833985583 for ; Sun, 1 Jan 2017 15:15:48 +0000 (UTC) Received: by cvs.NetBSD.org (Postfix, from userid 500) id 45665FBA6; Sun, 1 Jan 2017 15:15:48 +0000 (UTC) Content-Transfer-Encoding: 7bit Content-Type: multipart/mixed; boundary="_----------=_1483283748252340" MIME-Version: 1.0 Date: Sun, 1 Jan 2017 15:15:48 +0000 From: "Roland Illig" Subject: CVS commit: pkgsrc/pkgtools/pkglint To: pkgsrc-changes@NetBSD.org Reply-To: rillig@netbsd.org X-Mailer: log_accum Message-Id: <20170101151548.45665FBA6@cvs.NetBSD.org> Sender: pkgsrc-changes-owner@NetBSD.org List-Id: pkgsrc-changes.NetBSD.org Precedence: bulk This is a multi-part message in MIME format. --_----------=_1483283748252340 Content-Disposition: inline Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" Module Name: pkgsrc Committed By: rillig Date: Sun Jan 1 15:15:48 UTC 2017 Modified Files: pkgsrc/pkgtools/pkglint: Makefile pkgsrc/pkgtools/pkglint/files: buildlink3.go category_test.go files.go globaldata.go licenses.go line.go mkline.go mkline_test.go mklines.go mkshparser_test.go package.go parser.go patches.go patches_test.go pkglint.go pkglint_test.go plist.go shell_test.go shtokenizer_test.go toplevel_test.go util.go vartype.go vartypecheck.go Added Files: pkgsrc/pkgtools/pkglint/files/getopt: getopt.go getopt_test.go pkgsrc/pkgtools/pkglint/files/pkgver: vercmp.go vercmp_test.go Removed Files: pkgsrc/pkgtools/pkglint/files: dir.go dir_test.go getopt.go getopt_test.go main.go main_test.go vercmp.go vercmp_test.go Log Message: Cleaned up and refactored code. The getopt and pkgver code have been extracted to separate packages to make them reusable. Several other functions have been moved to make the structure easier to understand: * dir.go and main.go have been moved to pkglint.go * utility functions from pkglint.go have been moved to mkline.go Now pkglint.go contains only high-level code. To generate a diff of this commit: cvs rdiff -u -r1.505 -r1.506 pkgsrc/pkgtools/pkglint/Makefile cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/buildlink3.go \ pkgsrc/pkgtools/pkglint/files/licenses.go \ pkgsrc/pkgtools/pkglint/files/patches_test.go cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/category_test.go \ pkgsrc/pkgtools/pkglint/files/mkshparser_test.go cvs rdiff -u -r1.5 -r0 pkgsrc/pkgtools/pkglint/files/dir.go cvs rdiff -u -r1.4 -r0 pkgsrc/pkgtools/pkglint/files/dir_test.go \ pkgsrc/pkgtools/pkglint/files/getopt_test.go \ pkgsrc/pkgtools/pkglint/files/vercmp_test.go cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/files.go \ pkgsrc/pkgtools/pkglint/files/pkglint_test.go cvs rdiff -u -r1.6 -r0 pkgsrc/pkgtools/pkglint/files/getopt.go cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/globaldata.go cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/line.go cvs rdiff -u -r1.9 -r0 pkgsrc/pkgtools/pkglint/files/main.go cvs rdiff -u -r1.3 -r0 pkgsrc/pkgtools/pkglint/files/main_test.go cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/mkline.go cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/mkline_test.go cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/mklines.go \ pkgsrc/pkgtools/pkglint/files/plist.go \ pkgsrc/pkgtools/pkglint/files/vartype.go cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/package.go cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/parser.go \ pkgsrc/pkgtools/pkglint/files/toplevel_test.go cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/patches.go \ pkgsrc/pkgtools/pkglint/files/util.go cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/pkglint.go cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/shell_test.go cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/vartypecheck.go cvs rdiff -u -r1.2 -r0 pkgsrc/pkgtools/pkglint/files/vercmp.go cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go \ pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go \ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. --_----------=_1483283748252340 Content-Disposition: inline Content-Length: 66662 Content-Transfer-Encoding: binary Content-Type: text/x-diff; charset=utf-8 Modified files: Index: pkgsrc/pkgtools/pkglint/Makefile diff -u pkgsrc/pkgtools/pkglint/Makefile:1.505 pkgsrc/pkgtools/pkglint/Makefile:1.506 --- pkgsrc/pkgtools/pkglint/Makefile:1.505 Sun Jan 1 14:47:45 2017 +++ pkgsrc/pkgtools/pkglint/Makefile Sun Jan 1 15:15:47 2017 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.505 2017/01/01 14:47:45 rillig Exp $ +# $NetBSD: Makefile,v 1.506 2017/01/01 15:15:47 rillig Exp $ PKGNAME= pkglint-5.4.14 DISTFILES= # none @@ -10,7 +10,6 @@ COMMENT= Verifier for NetBSD packages LICENSE= 2-clause-bsd CONFLICTS+= pkglint4-[0-9]* -WRKSRC= ${WRKDIR}/netbsd.org/pkglint NO_CHECKSUM= yes USE_LANGUAGES= c USE_TOOLS+= pax @@ -19,35 +18,37 @@ GO_SRCPATH= netbsd.org/pkglint SUBST_CLASSES+= pkglint SUBST_STAGE.pkglint= post-configure -SUBST_FILES.pkglint+= main.go package_test.go +SUBST_FILES.pkglint+= pkglint.go package_test.go SUBST_SED.pkglint+= -e s\|@VERSION@\|${PKGNAME:S/pkglint-//}\|g SUBST_SED.pkglint+= -e s\|@BMAKE@\|${MAKE:Q}\|g do-extract: - ${RUN} mkdir -p ${WRKDIR}/pkglint/plist-clash - ${RUN} cd ${FILESDIR} && ${PAX} -rw *.go *.y */*.go pkglint.[01] ${WRKDIR}/pkglint + ${RUN} cd ${FILESDIR} && ${PAX} -rw . ${WRKDIR}/pkglint pre-build: ${RUN} env GOPATH=${WRKDIR}:${BUILDLINK_DIR}/gopkg go generate ${GO_BUILD_PATTERN} +pre-install: + ${RUN} rm -rf ${WRKDIR}/pkg + do-install: do-install-man .include "../../mk/bsd.prefs.mk" do-install-man: .PHONY -.if !empty(MANINSTALL:Mcatinstall) -. if defined(CATMAN_SECTION_SUFFIX) && !empty(CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss]) +.if ${MANINSTALL:Mcatinstall} +. if ${CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss]} ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1/pkglint.1 . else ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1 . endif .endif -.if !empty(MANINSTALL:Mmaninstall) +.if ${MANINSTALL:Mmaninstall} ${INSTALL_MAN} ${WRKSRC}/pkglint.1 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1 .endif .include "../../lang/go/go-package.mk" .if !empty(PKGSRC_RUN_TEST:M[yY][eE][sS]) -.include "../../devel/go-check/buildlink3.mk" +. include "../../devel/go-check/buildlink3.mk" .endif .include "../../mk/bsd.pkg.mk" Index: pkgsrc/pkgtools/pkglint/files/buildlink3.go diff -u pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.7 pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.8 --- pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.7 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/buildlink3.go Sun Jan 1 15:15:47 2017 @@ -1,6 +1,7 @@ package main import ( + "netbsd.org/pkglint/pkgver" "strings" ) @@ -139,7 +140,7 @@ func ChecklinesBuildlink3Mk(mklines *MkL if doCheck { if abi != nil && abi.lower != "" && !containsVarRef(abi.lower) { if api != nil && api.lower != "" && !containsVarRef(api.lower) { - if pkgverCmp(abi.lower, api.lower) < 0 { + if pkgver.Compare(abi.lower, api.lower) < 0 { abiLine.Warnf("ABI version %q should be at least API version %q (see %s).", abi.lower, api.lower, apiLine.ReferenceFrom(abiLine)) } Index: pkgsrc/pkgtools/pkglint/files/licenses.go diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.7 pkgsrc/pkgtools/pkglint/files/licenses.go:1.8 --- pkgsrc/pkgtools/pkglint/files/licenses.go:1.7 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/licenses.go Sun Jan 1 15:15:47 2017 @@ -119,7 +119,7 @@ func (lc *LicenseChecker) checkNode(cond var licenseFile string if G.Pkg != nil { if licenseFileValue, ok := G.Pkg.varValue("LICENSE_FILE"); ok { - licenseFile = G.CurrentDir + "/" + resolveVarsInRelativePath(licenseFileValue, false) + licenseFile = G.CurrentDir + "/" + lc.MkLine.resolveVarsInRelativePath(licenseFileValue, false) } } if licenseFile == "" { Index: pkgsrc/pkgtools/pkglint/files/patches_test.go diff -u pkgsrc/pkgtools/pkglint/files/patches_test.go:1.7 pkgsrc/pkgtools/pkglint/files/patches_test.go:1.8 --- pkgsrc/pkgtools/pkglint/files/patches_test.go:1.7 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/patches_test.go Sun Jan 1 15:15:47 2017 @@ -117,7 +117,7 @@ func (s *Suite) Test_ChecklinesPatch__er lines := s.NewLines("patch-ErrorCode", "$"+"NetBSD$", "", - "*** Error code 1", // Looks like a context diff, but isn’t. + "*** Error code 1", // Looks like a context diff, but isn't. "", "--- file.orig", "+++ file", @@ -346,7 +346,7 @@ func (s *Suite) Test_ChecklinesPatch__em } // In some context lines, the leading space character is missing. -// Since this is no problem for patch(1), pkglint also doesn’t complain. +// Since this is no problem for patch(1), pkglint also doesn't complain. func (s *Suite) Test_ChecklinesPatch__context_lines_with_tab_instead_of_space(c *check.C) { lines := s.NewLines("patch-aa", "$"+"NetBSD$", Index: pkgsrc/pkgtools/pkglint/files/category_test.go diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.4 pkgsrc/pkgtools/pkglint/files/category_test.go:1.5 --- pkgsrc/pkgtools/pkglint/files/category_test.go:1.4 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/category_test.go Sun Jan 1 15:15:47 2017 @@ -11,7 +11,7 @@ func (s *Suite) Test_CheckdirCategory_to "# $\n"+ "SUBDIR+=pkg1\n"+ "SUBDIR+=\u0020aaaaa\n"+ - "SUBDIR-=unknown #doesn’t work\n"+ + "SUBDIR-=unknown #doesn\u2019t work\n"+ "\n"+ ".include \"../mk/category.mk\"\n") Index: pkgsrc/pkgtools/pkglint/files/mkshparser_test.go diff -u pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.4 pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.5 --- pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.4 Sun Jul 10 21:24:47 2016 +++ pkgsrc/pkgtools/pkglint/files/mkshparser_test.go Sun Jan 1 15:15:47 2017 @@ -263,7 +263,7 @@ func (s *ShSuite) Test_ShellParser_for_c b.Words("in", "esac"), b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSemicolon()))) - // No semicolon necessary between the two “done”. + // No semicolon necessary between the two "done". s.test("for i in 1; do for j in 1; do echo $$i$$j; done done", b.List().AddCommand(b.For( "i", @@ -323,7 +323,7 @@ func (s *ShSuite) Test_ShellParser_if_cl b.List().AddCommand(b.SimpleCommand("echo", "yes")).AddSemicolon(), b.List().AddCommand(b.SimpleCommand("echo", "no")).AddSemicolon()))) - // No semicolon necessary between the two “fi”. + // No semicolon necessary between the two "fi". s.test("if cond1; then if cond2; then action; fi fi", b.List().AddCommand(b.If( b.List().AddCommand(b.SimpleCommand("cond1")).AddSemicolon(), Index: pkgsrc/pkgtools/pkglint/files/files.go diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.8 pkgsrc/pkgtools/pkglint/files/files.go:1.9 --- pkgsrc/pkgtools/pkglint/files/files.go:1.8 Sat Dec 17 13:35:32 2016 +++ pkgsrc/pkgtools/pkglint/files/files.go Sun Jan 1 15:15:47 2017 @@ -6,8 +6,8 @@ import ( "strings" ) -func LoadNonemptyLines(fname string, joinContinuationLines bool) []*Line { - lines, err := readLines(fname, joinContinuationLines) +func LoadNonemptyLines(fname string, joinBackslashLines bool) []*Line { + lines, err := readLines(fname, joinBackslashLines) if err != nil { NewLineWhole(fname).Errorf("Cannot be read.") return nil @@ -19,8 +19,8 @@ func LoadNonemptyLines(fname string, joi return lines } -func LoadExistingLines(fname string, foldBackslashLines bool) []*Line { - lines, err := readLines(fname, foldBackslashLines) +func LoadExistingLines(fname string, joinBackslashLines bool) []*Line { + lines, err := readLines(fname, joinBackslashLines) if err != nil { NewLineWhole(fname).Fatalf("Cannot be read.") } @@ -101,16 +101,16 @@ func splitRawLine(textnl string) (leadin return } -func readLines(fname string, joinContinuationLines bool) ([]*Line, error) { +func readLines(fname string, joinBackslashLines bool) ([]*Line, error) { rawText, err := ioutil.ReadFile(fname) if err != nil { return nil, err } - return convertToLogicalLines(fname, string(rawText), joinContinuationLines), nil + return convertToLogicalLines(fname, string(rawText), joinBackslashLines), nil } -func convertToLogicalLines(fname string, rawText string, joinContinuationLines bool) []*Line { +func convertToLogicalLines(fname string, rawText string, joinBackslashLines bool) []*Line { var rawLines []*RawLine for lineno, rawLine := range strings.SplitAfter(rawText, "\n") { if rawLine != "" { @@ -119,7 +119,7 @@ func convertToLogicalLines(fname string, } var loglines []*Line - if joinContinuationLines { + if joinBackslashLines { for lineno := 0; lineno < len(rawLines); { loglines = append(loglines, getLogicalLine(fname, rawLines, &lineno)) } Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.8 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.9 --- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.8 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go Sun Jan 1 15:15:47 2017 @@ -4,8 +4,77 @@ import ( "strings" check "gopkg.in/check.v1" + "os" ) +func (s *Suite) Test_Pkglint_Main_help(c *check.C) { + exitcode := new(Pkglint).Main("pkglint", "-h") + + c.Check(exitcode, equals, 0) + c.Check(s.Output(), check.Matches, `^\Qusage: pkglint [options] dir...\E\n(?s).+`) +} + +func (s *Suite) Test_Pkglint_Main_version(c *check.C) { + exitcode := new(Pkglint).Main("pkglint", "--version") + + c.Check(exitcode, equals, 0) + c.Check(s.Output(), equals, confVersion+"\n") +} + +func (s *Suite) Test_Pkglint_Main_no_args(c *check.C) { + exitcode := new(Pkglint).Main("pkglint") + + c.Check(exitcode, equals, 1) + c.Check(s.Stderr(), equals, "FATAL: \".\" is not inside a pkgsrc tree.\n") +} + +// go test -c -covermode count +// pkgsrcdir=... +// env PKGLINT_TESTCMDLINE="$pkgsrcdir -r" ./pkglint.test -test.coverprofile pkglint.cov -check.f TestRunPkglint +// go tool cover -html=pkglint.cov -o coverage.html +func (s *Suite) Test_Pkglint_coverage(c *check.C) { + cmdline := os.Getenv("PKGLINT_TESTCMDLINE") + if cmdline != "" { + G.logOut, G.logErr, G.debugOut = os.Stdout, os.Stderr, os.Stdout + new(Pkglint).Main(append([]string{"pkglint"}, splitOnSpace(cmdline)...)...) + } +} + +func (s *Suite) Test_Pkglint_CheckDirent__outside(c *check.C) { + s.Init(c) + s.CreateTmpFile("empty", "") + + new(Pkglint).CheckDirent(s.tmpdir) + + c.Check(s.Output(), equals, "ERROR: ~: Cannot determine the pkgsrc root directory for \"~\".\n") +} + +func (s *Suite) Test_Pkglint_CheckDirent(c *check.C) { + s.Init(c) + s.CreateTmpFile("mk/bsd.pkg.mk", "") + s.CreateTmpFile("category/package/Makefile", "") + s.CreateTmpFile("category/Makefile", "") + s.CreateTmpFile("Makefile", "") + G.globalData.Pkgsrcdir = s.tmpdir + pkglint := new(Pkglint) + + pkglint.CheckDirent(s.tmpdir) + + c.Check(s.Output(), equals, "ERROR: ~/Makefile: Must not be empty.\n") + + pkglint.CheckDirent(s.tmpdir + "/category") + + c.Check(s.Output(), equals, "ERROR: ~/category/Makefile: Must not be empty.\n") + + pkglint.CheckDirent(s.tmpdir + "/category/package") + + c.Check(s.Output(), equals, "ERROR: ~/category/package/Makefile: Must not be empty.\n") + + pkglint.CheckDirent(s.tmpdir + "/category/package/nonexistent") + + c.Check(s.Output(), equals, "ERROR: ~/category/package/nonexistent: No such file or directory.\n") +} + func (s *Suite) Test_resolveVariableRefs__circular_reference(c *check.C) { mkline := NewMkLine(NewLine("fname", 1, "GCC_VERSION=${GCC_VERSION}", nil)) G.Pkg = NewPackage(".") @@ -40,41 +109,6 @@ func (s *Suite) Test_resolveVariableRefs c.Check(resolved, equals, "gst-plugins0.10-x11/distinfo") } -func (s *Suite) Test_MatchVarassign(c *check.C) { - checkVarassign := func(text string, ck check.Checker, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) { - type va struct { - varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string - } - expected := va{varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment} - am, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text) - if !am { - c.Errorf("Text %q doesn’t match variable assignment", text) - return - } - actual := va{avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment} - c.Check(actual, ck, expected) - } - checkNotVarassign := func(text string) { - m, _, _, _, _, _, _, _ := MatchVarassign(text) - if m { - c.Errorf("Text %q matches variable assignment, but shouldn’t.", text) - } - } - - checkVarassign("C++=c11", equals, "C+", "", "+=", "C++=", "c11", "", "") - checkVarassign("V=v", equals, "V", "", "=", "V=", "v", "", "") - checkVarassign("VAR=#comment", equals, "VAR", "", "=", "VAR=", "", "", "#comment") - checkVarassign("VAR=\\#comment", equals, "VAR", "", "=", "VAR=", "#comment", "", "") - checkVarassign("VAR=\\\\\\##comment", equals, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment") - checkVarassign("VAR=\\", equals, "VAR", "", "=", "VAR=", "\\", "", "") - checkVarassign("VAR += value", equals, "VAR", " ", "+=", "VAR += ", "value", "", "") - checkVarassign(" VAR=value", equals, "VAR", "", "=", " VAR=", "value", "", "") - checkVarassign("VAR=value #comment", equals, "VAR", "", "=", "VAR=", "value", " ", "#comment") - checkNotVarassign("\tVAR=value") - checkNotVarassign("?=value") - checkNotVarassign("<=value") -} - func (s *Suite) Test_ChecklinesDescr(c *check.C) { lines := s.NewLines("DESCR", strings.Repeat("X", 90), Index: pkgsrc/pkgtools/pkglint/files/globaldata.go diff -u pkgsrc/pkgtools/pkglint/files/globaldata.go:1.17 pkgsrc/pkgtools/pkglint/files/globaldata.go:1.18 --- pkgsrc/pkgtools/pkglint/files/globaldata.go:1.17 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/globaldata.go Sun Jan 1 15:15:47 2017 @@ -230,7 +230,7 @@ func (gd *GlobalData) loadTools() { // Some user-defined variables do not influence the binary // package at all and therefore do not have to be added to - // BUILD_DEFS; therefore they are marked as “already added”. + // BUILD_DEFS; therefore they are marked as "already added". systemBuildDefs["DISTDIR"] = true systemBuildDefs["FETCH_CMD"] = true systemBuildDefs["FETCH_OUTPUT_ARGS"] = true Index: pkgsrc/pkgtools/pkglint/files/line.go diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.12 pkgsrc/pkgtools/pkglint/files/line.go:1.13 --- pkgsrc/pkgtools/pkglint/files/line.go:1.12 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/line.go Sun Jan 1 15:15:47 2017 @@ -10,7 +10,7 @@ package main // do not. // // Some methods allow modification of the raw lines contained in the -// logical line, but leave the “text” field untouched. These methods are +// logical line, but leave the Text field untouched. These methods are // used in the --autofix mode. import ( @@ -52,7 +52,7 @@ func NewLineMulti(fname string, firstLin return &Line{fname, int32(firstLine), int32(lastLine), text, rawLines, false, nil, nil, ""} } -// NewLineEOF creates a dummy line for logging, with the “line number” EOF. +// NewLineEOF creates a dummy line for logging, with the "line number" EOF. func NewLineEOF(fname string) *Line { return NewLineMulti(fname, -1, 0, "", nil) } Index: pkgsrc/pkgtools/pkglint/files/mkline.go diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.19 pkgsrc/pkgtools/pkglint/files/mkline.go:1.20 --- pkgsrc/pkgtools/pkglint/files/mkline.go:1.19 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/mkline.go Sun Jan 1 15:15:47 2017 @@ -216,7 +216,7 @@ func (mkline *MkLine) checkInclude() { includefile := mkline.Includefile() mustExist := mkline.MustExist() if G.opts.Debug { - traceStep1("includefile=%s", includefile) + traceStep2("includingFile=%s includefile=%s", mkline.Fname, includefile) } mkline.CheckRelativePath(includefile, mustExist) @@ -1106,6 +1106,44 @@ func (mkline *MkLine) withoutMakeVariabl } } +func (mkline *MkLine) resolveVarsInRelativePath(relpath string, adjustDepth bool) string { + tmp := relpath + tmp = strings.Replace(tmp, "${PKGSRCDIR}", G.CurPkgsrcdir, -1) + tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1) + tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1) + if contains(tmp, "${LUA_PKGSRCDIR}") { + tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.globalData.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1) + } + if contains(tmp, "${PHPPKGSRCDIR}") { + tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.globalData.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1) + } + if contains(tmp, "${SUSE_DIR_PREFIX}") { + suseDirPrefix := G.globalData.Latest("emulators", `^(suse[0-9]+)_base`, "$1") + tmp = strings.Replace(tmp, "${SUSE_DIR_PREFIX}", suseDirPrefix, -1) + } + if contains(tmp, "${PYPKGSRCDIR}") { + tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1) + } + if contains(tmp, "${PYPACKAGE}") { + tmp = strings.Replace(tmp, "${PYPACKAGE}", G.globalData.Latest("lang", `^python[0-9]+$`, "$0"), -1) + } + if G.Pkg != nil { + tmp = strings.Replace(tmp, "${FILESDIR}", G.Pkg.Filesdir, -1) + tmp = strings.Replace(tmp, "${PKGDIR}", G.Pkg.Pkgdir, -1) + } + + if adjustDepth { + if m, pkgpath := match1(tmp, `^\.\./\.\./([^.].*)$`); m { + tmp = G.CurPkgsrcdir + "/" + pkgpath + } + } + + if G.opts.Debug { + traceStep2("resolveVarsInRelativePath: %q => %q", relpath, tmp) + } + return tmp +} + func (mkline *MkLine) checkText(text string) { if G.opts.Debug { defer tracecall1(text)() @@ -1262,8 +1300,12 @@ func (mkline *MkLine) explainRelativeDir } func (mkline *MkLine) CheckRelativePkgdir(pkgdir string) { + if G.opts.Debug { + defer tracecall1(pkgdir)() + } + mkline.CheckRelativePath(pkgdir, true) - pkgdir = resolveVarsInRelativePath(pkgdir, false) + pkgdir = mkline.resolveVarsInRelativePath(pkgdir, false) if m, otherpkgpath := match1(pkgdir, `^(?:\./)?\.\./\.\./([^/]+/[^/]+)$`); m { if !fileExists(G.globalData.Pkgsrcdir + "/" + otherpkgpath + "/Makefile") { @@ -1280,11 +1322,15 @@ func (mkline *MkLine) CheckRelativePkgdi } func (mkline *MkLine) CheckRelativePath(path string, mustExist bool) { + if G.opts.Debug { + defer tracecall(path, mustExist)() + } + if !G.Wip && contains(path, "/wip/") { mkline.Line.Errorf("A main pkgsrc package must not depend on a pkgsrc-wip package.") } - resolvedPath := resolveVarsInRelativePath(path, true) + resolvedPath := mkline.resolveVarsInRelativePath(path, true) if containsVarRef(resolvedPath) { return } @@ -1621,7 +1667,7 @@ func (mkline *MkLine) determineUsedVaria // VarUseContext defines the context in which a variable is defined // or used. Whether that is allowed depends on: // -// * The variable’s data type, as defined in vardefs.go. +// * The variable's data type, as defined in vardefs.go. // * When used on the right-hand side of an assigment, the variable can // represent a list of words, a single word or even only part of a // word. This distinction decides upon the correct use of the :Q @@ -1629,7 +1675,7 @@ func (mkline *MkLine) determineUsedVaria // * When used in preprocessing statements like .if or .for, the other // operands of that statement should fit to the variable and are // checked against the variable type. For example, comparing OPSYS to -// x86_64 doesn’t make sense. +// x86_64 doesn't make sense. type VarUseContext struct { vartype *Vartype time vucTime @@ -1649,7 +1695,7 @@ const ( vucTimeParse // All files have been read, all variables can be referenced. - // Variable values don’t change anymore. + // Variable values don't change anymore. vucTimeRun ) @@ -1756,6 +1802,92 @@ func (ind *Indentation) Varnames() strin return varnames } +func MatchVarassign(text string) (m bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) { + i, n := 0, len(text) + + for i < n && text[i] == ' ' { + i++ + } + + varnameStart := i + for ; i < n; i++ { + b := text[i] + switch { + case 'A' <= b && b <= 'Z', + 'a' <= b && b <= 'z', + b == '_', + '0' <= b && b <= '9', + '$' <= b && b <= '.' && (b == '$' || b == '*' || b == '+' || b == '-' || b == '.'), + b == '[', + b == '{', b == '}': + continue + } + break + } + varnameEnd := i + + if varnameEnd == varnameStart { + return + } + + for i < n && (text[i] == ' ' || text[i] == '\t') { + i++ + } + + opStart := i + if i < n { + if b := text[i]; b == '!' || b == '+' || b == ':' || b == '?' { + i++ + } + } + if i < n && text[i] == '=' { + i++ + } else { + return + } + opEnd := i + + if text[varnameEnd-1] == '+' && varnameEnd == opStart && text[opStart] == '=' { + varnameEnd-- + opStart-- + } + + for i < n && (text[i] == ' ' || text[i] == '\t') { + i++ + } + + valueStart := i + valuebuf := make([]byte, n-valueStart) + j := 0 + for ; i < n; i++ { + b := text[i] + if b == '#' && (i == valueStart || text[i-1] != '\\') { + break + } else if b != '\\' || i+1 >= n || text[i+1] != '#' { + valuebuf[j] = b + j++ + } + } + + commentStart := i + for text[i-1] == ' ' || text[i-1] == '\t' { + i-- + } + valueEnd := i + + commentEnd := n + + m = true + varname = text[varnameStart:varnameEnd] + spaceAfterVarname = text[varnameEnd:opStart] + op = text[opStart:opEnd] + valueAlign = text[0:valueStart] + value = strings.TrimSpace(string(valuebuf[:j])) + spaceAfterValue = text[valueEnd:commentStart] + comment = text[commentStart:commentEnd] + return +} + func MatchMkInclude(text string) (m bool, indentation, directive, filename string) { return match3(text, `^\.(\s*)(s?include)\s+\"([^\"]+)\"\s*(?:#.*)?$`) } Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.20 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.21 --- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.20 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/mkline_test.go Sun Jan 1 15:15:47 2017 @@ -414,7 +414,7 @@ func (s *Suite) Test_MkLine_CheckVaruseP mklines.Check() - c.Check(s.Output(), equals, "") // Don’t warn that “.CURDIR should not be evaluated at load time.” + c.Check(s.Output(), equals, "") // Don't warn that ".CURDIR should not be evaluated at load time." } func (s *Suite) Test_MkLine_WarnVaruseLocalbase(c *check.C) { @@ -606,7 +606,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.mklines[1].Check() - c.Check(s.Output(), equals, "") // Don’t suggest to use ${HOMEPAGE:Q}. + c.Check(s.Output(), equals, "") // Don't suggest to use ${HOMEPAGE:Q}. } // Pkglint currently does not parse $$(subshell) commands very well. As @@ -628,7 +628,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.mklines[1].Check() G.Mk.mklines[2].Check() - c.Check(s.Output(), equals, "WARN: xpi.mk:2: Invoking subshells via $(...) is not portable enough.\n") // Don’t suggest to use ${AWK:Q}. + c.Check(s.Output(), equals, "WARN: xpi.mk:2: Invoking subshells via $(...) is not portable enough.\n") // Don't suggest to use ${AWK:Q}. } // LDFLAGS (and even more so CPPFLAGS and CFLAGS) may contain special @@ -682,7 +682,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.mklines[1].Check() - c.Check(s.Output(), equals, "") // Don’t suggest ${ECHO:Q} here. + c.Check(s.Output(), equals, "") // Don't suggest ${ECHO:Q} here. } func (s *Suite) Test_MkLine_variableNeedsQuoting__LDADD_in_BUILDLINK_TRANSFORM(c *check.C) { @@ -707,7 +707,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.mklines[0].Check() - c.Check(s.Output(), equals, "") // Don’t suggest ${REPLACE_PERL:Q}. + c.Check(s.Output(), equals, "") // Don't suggest ${REPLACE_PERL:Q}. } func (s *Suite) Test_MkLine_variableNeedsQuoting_guessed_list_variable_in_quotes(c *check.C) { @@ -734,7 +734,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.Check() - c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators. + c.Check(s.Output(), equals, "") // Don't warn about missing :Q modifiers. } func (s *Suite) Test_MkLine_variableNeedsQuoting_PKGNAME_and_URL_list_in_URL_list(c *check.C) { @@ -748,7 +748,7 @@ func (s *Suite) Test_MkLine_variableNeed G.Mk.mklines[1].checkVarassignVaruse() - c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators. + c.Check(s.Output(), equals, "") // Don't warn about missing :Q modifiers. } func (s *Suite) Test_MkLine_variableNeedsQuoting_tool_in_CONFIGURE_ENV(c *check.C) { @@ -779,7 +779,7 @@ func (s *Suite) Test_MkLine_Varuse_Modif G.Mk.mklines[0].Check() - c.Check(s.Output(), equals, "") // Don’t warn that ${XKBBASE}/xkbcomp is used but not defined. + c.Check(s.Output(), equals, "") // Don't warn that ${XKBBASE}/xkbcomp is used but not defined. } func (s *Suite) Test_MkLine_CheckCond_comparison_with_shell_command(c *check.C) { @@ -793,7 +793,7 @@ func (s *Suite) Test_MkLine_CheckCond_co G.Mk.Check() - // Don’t warn about unknown shell command "cc". + // Don't warn about unknown shell command "cc". c.Check(s.Output(), equals, "WARN: security/openssl/Makefile:2: Use ${PKGSRC_COMPILER:Mgcc} instead of the == operator.\n") } @@ -924,6 +924,41 @@ func (s *Suite) Test_MkLine__comment_in_ c.Check(s.Output(), equals, "WARN: Makefile:2: The # character starts a comment.\n") } +func (s *Suite) Test_MatchVarassign(c *check.C) { + checkVarassign := func(text string, ck check.Checker, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) { + type va struct { + varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string + } + expected := va{varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment} + am, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text) + if !am { + c.Errorf("Text %q doesn't match variable assignment", text) + return + } + actual := va{avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment} + c.Check(actual, ck, expected) + } + checkNotVarassign := func(text string) { + m, _, _, _, _, _, _, _ := MatchVarassign(text) + if m { + c.Errorf("Text %q matches variable assignment, but shouldn't.", text) + } + } + + checkVarassign("C++=c11", equals, "C+", "", "+=", "C++=", "c11", "", "") + checkVarassign("V=v", equals, "V", "", "=", "V=", "v", "", "") + checkVarassign("VAR=#comment", equals, "VAR", "", "=", "VAR=", "", "", "#comment") + checkVarassign("VAR=\\#comment", equals, "VAR", "", "=", "VAR=", "#comment", "", "") + checkVarassign("VAR=\\\\\\##comment", equals, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment") + checkVarassign("VAR=\\", equals, "VAR", "", "=", "VAR=", "\\", "", "") + checkVarassign("VAR += value", equals, "VAR", " ", "+=", "VAR += ", "value", "", "") + checkVarassign(" VAR=value", equals, "VAR", "", "=", " VAR=", "value", "", "") + checkVarassign("VAR=value #comment", equals, "VAR", "", "=", "VAR=", "value", " ", "#comment") + checkNotVarassign("\tVAR=value") + checkNotVarassign("?=value") + checkNotVarassign("<=value") +} + func (s *Suite) Test_Indentation(c *check.C) { ind := &Indentation{} Index: pkgsrc/pkgtools/pkglint/files/mklines.go diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.10 pkgsrc/pkgtools/pkglint/files/mklines.go:1.11 --- pkgsrc/pkgtools/pkglint/files/mklines.go:1.10 Tue Nov 1 21:40:25 2016 +++ pkgsrc/pkgtools/pkglint/files/mklines.go Sun Jan 1 15:15:47 2017 @@ -310,7 +310,7 @@ func (va *VaralignBlock) fixalign(mkline return } - // Don’t warn about procedure parameters + // Don't warn about procedure parameters if mkline.Op() == opAssignEval && matches(mkline.Varname(), `^[a-z]`) { return } Index: pkgsrc/pkgtools/pkglint/files/plist.go diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.10 pkgsrc/pkgtools/pkglint/files/plist.go:1.11 --- pkgsrc/pkgtools/pkglint/files/plist.go:1.10 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/plist.go Sun Jan 1 15:15:47 2017 @@ -512,7 +512,7 @@ func (s *plistLineSorter) Sort() { firstLine := s.first.line firstLine.RememberAutofix("Sorting the whole file.") firstLine.logAutofix() - firstLine.changed = true // Otherwise the changes won’t be saved + firstLine.changed = true // Otherwise the changes won't be saved lines := []*Line{firstLine} lines = append(lines, s.after[s.first]...) for _, pline := range s.plines { Index: pkgsrc/pkgtools/pkglint/files/vartype.go diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.10 pkgsrc/pkgtools/pkglint/files/vartype.go:1.11 --- pkgsrc/pkgtools/pkglint/files/vartype.go:1.10 Sun Jul 10 21:24:47 2016 +++ pkgsrc/pkgtools/pkglint/files/vartype.go Sun Jan 1 15:15:47 2017 @@ -101,7 +101,7 @@ func (vt *Vartype) AllowedFiles(perms Ac } // Returns whether the type is considered a shell list. -// This distinction between “real lists” and “considered a list” makes +// This distinction between "real lists" and "considered a list" makes // the implementation of checklineMkVartype easier. func (vt *Vartype) IsConsideredList() bool { switch vt.kindOfList { @@ -147,8 +147,8 @@ func (vt *Vartype) IsShell() bool { return false } -// The basic vartype consists only of characters that don’t -// need escaping in most contexts, like A-Za-z0-9-_. +// IsBasicSafe returns whether the basic vartype consists only of +// characters that don't need escaping in most contexts, like A-Za-z0-9-_. func (vt *Vartype) IsBasicSafe() bool { switch vt.basicType { case BtBuildlinkDepmethod, Index: pkgsrc/pkgtools/pkglint/files/package.go diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.14 pkgsrc/pkgtools/pkglint/files/package.go:1.15 --- pkgsrc/pkgtools/pkglint/files/package.go:1.14 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/package.go Sun Jan 1 15:15:47 2017 @@ -2,12 +2,15 @@ package main import ( "fmt" + "netbsd.org/pkglint/pkgver" "path" "regexp" "strconv" "strings" ) +const rePkgname = `^([\w\-.+]+)-(\d(?:\w|\.\d)*)$` + // Package contains data for the pkgsrc package that is currently checked. type Package struct { Pkgpath string // e.g. "category/pkgdir" @@ -104,7 +107,7 @@ func (pkg *Package) checkPossibleDowngra if change.Action == "Updated" { changeVersion := regcomp(`nb\d+$`).ReplaceAllString(change.Version, "") - if pkgverCmp(pkgversion, changeVersion) < 0 { + if pkgver.Compare(pkgversion, changeVersion) < 0 { mkline.Line.Warnf("The package is being downgraded from %s (see %s) to %s", change.Version, change.Line.ReferenceFrom(mkline.Line), pkgversion) Explain( "The files in doc/CHANGES-*, in which all version changes are", @@ -233,10 +236,10 @@ func (pkg *Package) loadPackageMakefile( allLines.DetermineUsedVariables() - pkg.Pkgdir = expandVariableWithDefault("PKGDIR", ".") - pkg.DistinfoFile = expandVariableWithDefault("DISTINFO_FILE", "distinfo") - pkg.Filesdir = expandVariableWithDefault("FILESDIR", "files") - pkg.Patchdir = expandVariableWithDefault("PATCHDIR", "patches") + pkg.Pkgdir = pkg.expandVariableWithDefault("PKGDIR", ".") + pkg.DistinfoFile = pkg.expandVariableWithDefault("DISTINFO_FILE", "distinfo") + pkg.Filesdir = pkg.expandVariableWithDefault("FILESDIR", "files") + pkg.Patchdir = pkg.expandVariableWithDefault("PATCHDIR", "patches") if varIsDefined("PHPEXT_MK") { if !varIsDefined("USE_PHP_EXT_PATCHES") { @@ -283,7 +286,7 @@ func (pkg *Package) readMakefile(fname s var includeFile, incDir, incBase string if mkline.IsInclude() { inc := mkline.Includefile() - includeFile = resolveVariableRefs(resolveVarsInRelativePath(inc, true)) + includeFile = resolveVariableRefs(mkline.resolveVarsInRelativePath(inc, true)) if containsVarRef(includeFile) { if !contains(fname, "/mk/") { line.Notef("Skipping include file %q. This may result in false warnings.", includeFile) @@ -326,7 +329,7 @@ func (pkg *Package) readMakefile(fname s // Only look in the directory relative to the // current file and in the current working directory. - // Pkglint doesn’t have an include dir list, like make(1) does. + // Pkglint doesn't have an include dir list, like make(1) does. if !fileExists(dirname + "/" + includeFile) { if dirname != G.CurrentDir { // Prevent unnecessary syscalls dirname = G.CurrentDir @@ -530,6 +533,23 @@ func (pkg *Package) pkgnameFromDistname( return result } +func (pkg *Package) expandVariableWithDefault(varname, defaultValue string) string { + mkline := G.Pkg.vardef[varname] + if mkline == nil { + return defaultValue + } + + value := mkline.Value() + value = mkline.resolveVarsInRelativePath(value, true) + if containsVarRef(value) { + value = resolveVariableRefs(value) + } + if G.opts.Debug { + traceStep2("Expanded %q to %q", varname, value) + } + return value +} + func (pkg *Package) checkUpdate() { if pkg.EffectivePkgbase != "" { for _, sugg := range G.globalData.GetSuggestedPackageUpdates() { @@ -543,7 +563,7 @@ func (pkg *Package) checkUpdate() { } pkgnameLine := pkg.EffectivePkgnameLine - cmp := pkgverCmp(pkg.EffectivePkgversion, suggver) + cmp := pkgver.Compare(pkg.EffectivePkgversion, suggver) switch { case cmp < 0: pkgnameLine.Warnf("This package should be updated to %s%s.", sugg.Version, comment) Index: pkgsrc/pkgtools/pkglint/files/parser.go diff -u pkgsrc/pkgtools/pkglint/files/parser.go:1.5 pkgsrc/pkgtools/pkglint/files/parser.go:1.6 --- pkgsrc/pkgtools/pkglint/files/parser.go:1.5 Sun Jun 5 11:24:32 2016 +++ pkgsrc/pkgtools/pkglint/files/parser.go Sun Jan 1 15:15:47 2017 @@ -48,6 +48,15 @@ func (p *Parser) PkgbasePattern() (pkgba } } +type DependencyPattern struct { + pkgbase string // "freeciv-client", "{gcc48,gcc48-libs}", "${EMACS_REQD}" + lowerOp string // ">=", ">" + lower string // "2.5.0", "${PYVER}" + upperOp string // "<", "<=" + upper string // "3.0", "${PYVER}" + wildcard string // "[0-9]*", "1.5.*", "${PYVER}" +} + func (p *Parser) Dependency() *DependencyPattern { repl := p.repl Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.5 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.6 --- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.5 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go Sun Jan 1 15:15:47 2017 @@ -14,7 +14,7 @@ func (s *Suite) Test_CheckdirToplevel(c "SUBDIR+=\tccc\n"+ "SUBDIR+=\tccc\n"+ "#SUBDIR+=\tignoreme\n"+ - "SUBDIR+=\tnonexisting\n"+ // This just doesn’t happen in practice. + "SUBDIR+=\tnonexisting\n"+ // This doesn't happen in practice, therefore no warning. "SUBDIR+=\tbbb\n") s.CreateTmpFile("archivers/Makefile", "") s.CreateTmpFile("bbb/Makefile", "") Index: pkgsrc/pkgtools/pkglint/files/patches.go diff -u pkgsrc/pkgtools/pkglint/files/patches.go:1.11 pkgsrc/pkgtools/pkglint/files/patches.go:1.12 --- pkgsrc/pkgtools/pkglint/files/patches.go:1.11 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/patches.go Sun Jan 1 15:15:47 2017 @@ -288,7 +288,7 @@ func guessFileType(line *Line, fname str } basename := path.Base(fname) - basename = strings.TrimSuffix(basename, ".in") // doesn’t influence the content type + basename = strings.TrimSuffix(basename, ".in") // doesn't influence the content type ext := strings.ToLower(strings.TrimLeft(path.Ext(basename), ".")) switch { Index: pkgsrc/pkgtools/pkglint/files/util.go diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.11 pkgsrc/pkgtools/pkglint/files/util.go:1.12 --- pkgsrc/pkgtools/pkglint/files/util.go:1.11 Sun Dec 4 15:28:36 2016 +++ pkgsrc/pkgtools/pkglint/files/util.go Sun Jan 1 15:15:47 2017 @@ -28,6 +28,13 @@ func ifelseStr(cond bool, a, b string) s return b } +func imax(a, b int) int { + if a > b { + return a + } + return b +} + func mustMatch(s string, re RegexPattern) []string { if m := match(s, re); m != nil { return m @@ -420,7 +427,7 @@ func (r Ref) String() string { return fmt.Sprintf("%v", ref) } -// Emulates make(1)’s :S substitution operator. +// Emulates make(1)'s :S substitution operator. func mkopSubst(s string, left bool, from string, right bool, to string, flags string) string { if G.opts.Debug { defer tracecall(s, left, from, right, to, flags)() Index: pkgsrc/pkgtools/pkglint/files/pkglint.go diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.16 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.17 --- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.16 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/pkglint.go Sun Jan 1 15:15:47 2017 @@ -1,14 +1,224 @@ package main import ( + "fmt" + "io" + "netbsd.org/pkglint/getopt" "os" + "os/user" "path" + "path/filepath" + "runtime/pprof" "strings" ) -const ( - rePkgname = `^([\w\-.+]+)-(\d(?:\w|\.\d)*)$` -) +const confMake = "@BMAKE@" +const confVersion = "@VERSION@" + +func main() { + G.logOut, G.logErr, G.debugOut = os.Stdout, os.Stderr, os.Stdout + os.Exit(new(Pkglint).Main(os.Args...)) +} + +type Pkglint struct{} + +func (pkglint *Pkglint) Main(args ...string) (exitcode int) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(pkglintFatal); ok { + exitcode = 1 + } else { + panic(r) + } + } + }() + + if exitcode := pkglint.ParseCommandLine(args); exitcode != nil { + return *exitcode + } + + if G.opts.Profiling { + f, err := os.Create("pkglint.pprof") + if err != nil { + dummyLine.Fatalf("Cannot create profiling file: %s", err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + G.rematch = NewHistogram() + G.renomatch = NewHistogram() + G.retime = NewHistogram() + G.loghisto = NewHistogram() + } + + for _, arg := range G.opts.args { + G.Todo = append(G.Todo, filepath.ToSlash(arg)) + } + if len(G.Todo) == 0 { + G.Todo = []string{"."} + } + + G.globalData.Initialize() + + currentUser, err := user.Current() + if err == nil { + // On Windows, this is `Computername\Username`. + G.CurrentUsername = regcomp(`^.*\\`).ReplaceAllString(currentUser.Username, "") + } + + for len(G.Todo) != 0 { + item := G.Todo[0] + G.Todo = G.Todo[1:] + pkglint.CheckDirent(item) + } + + checkToplevelUnusedLicenses() + pkglint.PrintSummary() + if G.opts.Profiling { + G.loghisto.PrintStats("loghisto", G.logOut, 0) + G.rematch.PrintStats("rematch", G.logOut, 10) + G.renomatch.PrintStats("renomatch", G.logOut, 10) + G.retime.PrintStats("retime", G.logOut, 10) + } + if G.errors != 0 { + return 1 + } + return 0 +} + +func (pkglint *Pkglint) ParseCommandLine(args []string) *int { + gopts := &G.opts + opts := getopt.NewOptions() + + check := opts.AddFlagGroup('C', "check", "check,...", "enable or disable specific checks") + opts.AddFlagVar('d', "debug", &gopts.Debug, false, "log verbose call traces for debugging") + opts.AddFlagVar('e', "explain", &gopts.Explain, false, "explain the diagnostics or give further help") + opts.AddFlagVar('f', "show-autofix", &gopts.PrintAutofix, false, "show what pkglint can fix automatically") + opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors (experimental)") + opts.AddFlagVar('g', "gcc-output-format", &gopts.GccOutput, false, "mimic the gcc output format") + opts.AddFlagVar('h', "help", &gopts.PrintHelp, false, "print a detailed usage message") + opts.AddFlagVar('I', "dumpmakefile", &gopts.DumpMakefile, false, "dump the Makefile after parsing") + opts.AddFlagVar('i', "import", &gopts.Import, false, "prepare the import of a wip package") + opts.AddFlagVar('m', "log-verbose", &gopts.LogVerbose, false, "allow the same log message more than once") + opts.AddFlagVar('p', "profiling", &gopts.Profiling, false, "profile the executing program") + opts.AddFlagVar('q', "quiet", &gopts.Quiet, false, "don't print a summary line when finishing") + opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too") + opts.AddFlagVar('s', "source", &gopts.PrintSource, false, "show the source lines together with diagnostics") + opts.AddFlagVar('V', "version", &gopts.PrintVersion, false, "print the version number of pkglint") + warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings") + + check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files") + check.AddFlagVar("bl3", &gopts.CheckBuildlink3, true, "check buildlink3.mk files") + check.AddFlagVar("DESCR", &gopts.CheckDescr, true, "check DESCR file") + check.AddFlagVar("distinfo", &gopts.CheckDistinfo, true, "check distinfo file") + check.AddFlagVar("extra", &gopts.CheckExtra, false, "check various additional files") + check.AddFlagVar("global", &gopts.CheckGlobal, false, "inter-package checks") + check.AddFlagVar("INSTALL", &gopts.CheckInstall, true, "check INSTALL and DEINSTALL scripts") + check.AddFlagVar("Makefile", &gopts.CheckMakefile, true, "check Makefiles") + check.AddFlagVar("MESSAGE", &gopts.CheckMessage, true, "check MESSAGE file") + check.AddFlagVar("mk", &gopts.CheckMk, true, "check other .mk files") + check.AddFlagVar("patches", &gopts.CheckPatches, true, "check patches") + check.AddFlagVar("PLIST", &gopts.CheckPlist, true, "check PLIST files") + + warn.AddFlagVar("absname", &gopts.WarnAbsname, true, "warn about use of absolute file names") + warn.AddFlagVar("directcmd", &gopts.WarnDirectcmd, true, "warn about use of direct command names instead of Make variables") + warn.AddFlagVar("extra", &gopts.WarnExtra, false, "enable some extra warnings") + warn.AddFlagVar("order", &gopts.WarnOrder, false, "warn if Makefile entries are unordered") + warn.AddFlagVar("perm", &gopts.WarnPerm, false, "warn about unforeseen variable definition and use") + warn.AddFlagVar("plist-depr", &gopts.WarnPlistDepr, false, "warn about deprecated paths in PLISTs") + warn.AddFlagVar("plist-sort", &gopts.WarnPlistSort, false, "warn about unsorted entries in PLISTs") + warn.AddFlagVar("quoting", &gopts.WarnQuoting, false, "warn about quoting issues") + warn.AddFlagVar("space", &gopts.WarnSpace, false, "warn about inconsistent use of white-space") + warn.AddFlagVar("style", &gopts.WarnStyle, false, "warn about stylistic issues") + warn.AddFlagVar("types", &gopts.WarnTypes, true, "do some simple type checking in Makefiles") + + remainingArgs, err := opts.Parse(args) + if err != nil { + fmt.Fprintf(G.logErr, "%s\n\n", err) + opts.Help(G.logErr, "pkglint [options] dir...") + exitcode := 1 + return &exitcode + } + gopts.args = remainingArgs + + if gopts.PrintHelp { + opts.Help(G.logOut, "pkglint [options] dir...") + exitcode := 0 + return &exitcode + } + + if G.opts.PrintVersion { + fmt.Fprintf(G.logOut, "%s\n", confVersion) + exitcode := 0 + return &exitcode + } + + return nil +} + +func (pkglint *Pkglint) PrintSummary() { + if !G.opts.Quiet { + if G.errors != 0 || G.warnings != 0 { + fmt.Fprintf(G.logOut, "%d %s and %d %s found.\n", + G.errors, ifelseStr(G.errors == 1, "error", "errors"), + G.warnings, ifelseStr(G.warnings == 1, "warning", "warnings")) + } else { + io.WriteString(G.logOut, "looks fine.\n") + } + if G.explanationsAvailable && !G.opts.Explain { + fmt.Fprint(G.logOut, "(Run \"pkglint -e\" to show explanations.)\n") + } + if G.autofixAvailable && !G.opts.PrintAutofix && !G.opts.Autofix { + fmt.Fprint(G.logOut, "(Run \"pkglint -fs\" to show what can be fixed automatically.)\n") + } + if G.autofixAvailable && !G.opts.Autofix { + fmt.Fprint(G.logOut, "(Run \"pkglint -F\" to automatically fix some issues.)\n") + } + } +} + +func (pkglint *Pkglint) CheckDirent(fname string) { + if G.opts.Debug { + defer tracecall1(fname)() + } + + st, err := os.Lstat(fname) + if err != nil || !st.Mode().IsDir() && !st.Mode().IsRegular() { + NewLineWhole(fname).Errorf("No such file or directory.") + return + } + isDir := st.Mode().IsDir() + isReg := st.Mode().IsRegular() + + G.CurrentDir = ifelseStr(isReg, path.Dir(fname), fname) + absCurrentDir := abspath(G.CurrentDir) + G.Wip = !G.opts.Import && matches(absCurrentDir, `/wip/|/wip$`) + G.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`) + G.CurPkgsrcdir = findPkgsrcTopdir(G.CurrentDir) + if G.CurPkgsrcdir == "" { + NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", G.CurrentDir) + return + } + + switch { + case isDir && isEmptyDir(fname): + return + case isReg: + Checkfile(fname) + return + } + + switch G.CurPkgsrcdir { + case "../..": + checkdirPackage(relpath(G.globalData.Pkgsrcdir, G.CurrentDir)) + case "..": + CheckdirCategory() + case ".": + CheckdirToplevel() + default: + NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.") + } +} // Returns the pkgsrc top-level directory, relative to the given file or directory. func findPkgsrcTopdir(fname string) string { @@ -53,23 +263,6 @@ func resolveVariableRefs(text string) st } } -func expandVariableWithDefault(varname, defaultValue string) string { - mkline := G.Pkg.vardef[varname] - if mkline == nil { - return defaultValue - } - - value := mkline.Value() - value = resolveVarsInRelativePath(value, true) - if containsVarRef(value) { - value = resolveVariableRefs(value) - } - if G.opts.Debug { - traceStep2("Expanded %q to %q", varname, value) - } - return value -} - func CheckfileExtra(fname string) { if G.opts.Debug { defer tracecall1(fname)() @@ -277,7 +470,7 @@ func Checkfile(fname string) { // Ok case hasPrefix(basename, "CHANGES-"): - // This only checks the file, but doesn’t register the changes globally. + // This only checks the file, but doesn't register the changes globally. G.globalData.loadDocChangesFromFile(fname) case matches(fname, `(?:^|/)files/[^/]*$`): @@ -304,136 +497,3 @@ func ChecklinesTrailingEmptyLines(lines lines[last].Notef("Trailing empty lines.") } } - -func MatchVarassign(text string) (m bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) { - i, n := 0, len(text) - - for i < n && text[i] == ' ' { - i++ - } - - varnameStart := i - for ; i < n; i++ { - b := text[i] - switch { - case 'A' <= b && b <= 'Z', - 'a' <= b && b <= 'z', - b == '_', - '0' <= b && b <= '9', - '$' <= b && b <= '.' && (b == '$' || b == '*' || b == '+' || b == '-' || b == '.'), - b == '[', - b == '{', b == '}': - continue - } - break - } - varnameEnd := i - - if varnameEnd == varnameStart { - return - } - - for i < n && (text[i] == ' ' || text[i] == '\t') { - i++ - } - - opStart := i - if i < n { - if b := text[i]; b == '!' || b == '+' || b == ':' || b == '?' { - i++ - } - } - if i < n && text[i] == '=' { - i++ - } else { - return - } - opEnd := i - - if text[varnameEnd-1] == '+' && varnameEnd == opStart && text[opStart] == '=' { - varnameEnd-- - opStart-- - } - - for i < n && (text[i] == ' ' || text[i] == '\t') { - i++ - } - - valueStart := i - valuebuf := make([]byte, n-valueStart) - j := 0 - for ; i < n; i++ { - b := text[i] - if b == '#' && (i == valueStart || text[i-1] != '\\') { - break - } else if b != '\\' || i+1 >= n || text[i+1] != '#' { - valuebuf[j] = b - j++ - } - } - - commentStart := i - for text[i-1] == ' ' || text[i-1] == '\t' { - i-- - } - valueEnd := i - - commentEnd := n - - m = true - varname = text[varnameStart:varnameEnd] - spaceAfterVarname = text[varnameEnd:opStart] - op = text[opStart:opEnd] - valueAlign = text[0:valueStart] - value = strings.TrimSpace(string(valuebuf[:j])) - spaceAfterValue = text[valueEnd:commentStart] - comment = text[commentStart:commentEnd] - return -} - -type DependencyPattern struct { - pkgbase string // "freeciv-client", "{gcc48,gcc48-libs}", "${EMACS_REQD}" - lowerOp string // ">=", ">" - lower string // "2.5.0", "${PYVER}" - upperOp string // "<", "<=" - upper string // "3.0", "${PYVER}" - wildcard string // "[0-9]*", "1.5.*", "${PYVER}" -} - -func resolveVarsInRelativePath(relpath string, adjustDepth bool) string { - tmp := relpath - tmp = strings.Replace(tmp, "${PKGSRCDIR}", G.CurPkgsrcdir, -1) - tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1) - tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1) - if contains(tmp, "${LUA_PKGSRCDIR}") { - tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.globalData.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1) - } - if contains(tmp, "${PHPPKGSRCDIR}") { - tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.globalData.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1) - } - if contains(tmp, "${SUSE_DIR_PREFIX}") { - suseDirPrefix := G.globalData.Latest("emulators", `^(suse[0-9]+)_base`, "$1") - tmp = strings.Replace(tmp, "${SUSE_DIR_PREFIX}", suseDirPrefix, -1) - } - if contains(tmp, "${PYPKGSRCDIR}") { - tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1) - } - if contains(tmp, "${PYPACKAGE}") { - tmp = strings.Replace(tmp, "${PYPACKAGE}", G.globalData.Latest("lang", `^python[0-9]+$`, "$0"), -1) - } - if G.Pkg != nil { - tmp = strings.Replace(tmp, "${FILESDIR}", G.Pkg.Filesdir, -1) - tmp = strings.Replace(tmp, "${PKGDIR}", G.Pkg.Pkgdir, -1) - } - - if adjustDepth { - if m, pkgpath := match1(tmp, `^\.\./\.\./([^.].*)$`); m { - tmp = G.CurPkgsrcdir + "/" + pkgpath - } - } - - if G.opts.Debug { - traceStep2("resolveVarsInRelativePath: %q => %q", relpath, tmp) - } - return tmp -} Index: pkgsrc/pkgtools/pkglint/files/shell_test.go diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.13 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.14 --- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.13 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sun Jan 1 15:15:47 2017 @@ -426,7 +426,7 @@ func (s *Suite) Test_ShellLine_checkComm func (s *Suite) Test_splitIntoMkWords(c *check.C) { url := "http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=" - words, rest := splitIntoShellTokens(dummyLine, url) // Doesn’t really make sense + words, rest := splitIntoShellTokens(dummyLine, url) // Doesn't really make sense c.Check(words, check.DeepEquals, []string{"http://registry.gimp.org/file/fix-ca.c?action=download", "&", "id=9884", "&", "file="}) c.Check(rest, equals, "") Index: pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.3 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.4 --- pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.3 Sat Jul 9 09:43:48 2016 +++ pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go Sun Jan 1 15:15:47 2017 @@ -291,7 +291,7 @@ func (s *Suite) Test_ShTokenizer_ShAtom( token(shtWord, "echo", shqDquotBackt), token(shtSpace, " ", shqDquotBackt), token(shtWord, "\"", shqDquotBacktDquot), - token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn’t influence parsing. + token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn't influence parsing. token(shtWord, "\"", shqDquotBackt), token(shtWord, "`", shqDquot), token(shtWord, "\"", shqPlain)) @@ -435,7 +435,7 @@ func (s *Suite) Test_ShTokenizer_ShToken NewShAtomVaruse("${PATH:Q}", shqPlain, "PATH", "Q")), NewShToken("true", NewShAtom(shtWord, "true", shqPlain))) - if false { // Don’t know how to tokenize this correctly. + if false { // Don't know how to tokenize this correctly. check("id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)", NewShToken("id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)", NewShAtom(shtWord, "id=", shqPlain), Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.21 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.22 --- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.21 Tue Dec 13 00:58:07 2016 +++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go Sun Jan 1 15:15:47 2017 @@ -591,7 +591,7 @@ func (cv *VartypeCheck) Option() { } if m, optname := match1(value, `^-?([a-z][-0-9a-z+]*)$`); m { - if _, found := G.globalData.PkgOptions[optname]; !found { // There’s a difference between empty and absent here. + if _, found := G.globalData.PkgOptions[optname]; !found { // There's a difference between empty and absent here. line.Warnf("Unknown option \"%s\".", optname) Explain( "This option is not documented in the mk/defaults/options.description", Added files: Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.1 --- /dev/null Sun Jan 1 15:15:48 2017 +++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go Sun Jan 1 15:15:47 2017 @@ -0,0 +1,236 @@ +package getopt + +// Self-written getopt to support multi-argument options. + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + "unicode/utf8" +) + +type Options struct { + options []*option +} + +func NewOptions() *Options { + return new(Options) +} + +func (o *Options) AddFlagGroup(shortName rune, longName, argDescription, description string) *FlagGroup { + grp := new(FlagGroup) + opt := &option{shortName, longName, argDescription, description, grp} + o.options = append(o.options, opt) + return grp +} + +func (o *Options) AddFlagVar(shortName rune, longName string, pflag *bool, defval bool, description string) { + *pflag = defval + opt := &option{shortName, longName, "", description, pflag} + o.options = append(o.options, opt) +} + +func (o *Options) Parse(args []string) (remainingArgs []string, err error) { + var skip int + for i := 1; i < len(args) && err == nil; i++ { + arg := args[i] + switch { + case arg == "--": + remainingArgs = append(remainingArgs, args[i+1:]...) + return + case strings.HasPrefix(arg, "--"): + skip, err = o.parseLongOption(args, i, arg[2:]) + i += skip + case strings.HasPrefix(arg, "-"): + skip, err = o.parseShortOptions(args, i, arg[1:]) + i += skip + default: + remainingArgs = append(remainingArgs, arg) + } + } + if err != nil { + err = optErr(args[0] + ": " + err.Error()) + } + return +} + +func (o *Options) parseLongOption(args []string, i int, argRest string) (skip int, err error) { + parts := strings.SplitN(argRest, "=", 2) + argname := parts[0] + var argval *string + if 1 < len(parts) { + argval = &parts[1] + } + + for _, opt := range o.options { + if argname == opt.longName { + return o.handleLongOption(args, i, opt, argval) + } + } + + var prefixOpt *option + for _, opt := range o.options { + if strings.HasPrefix(opt.longName, argname) { + if prefixOpt == nil { + prefixOpt = opt + } else { + return 0, optErr(fmt.Sprintf("ambiguous option: --%s could mean --%s or --%s", argRest, prefixOpt.longName, opt.longName)) + } + } + } + if prefixOpt != nil { + return o.handleLongOption(args, i, prefixOpt, argval) + } + return 0, optErr("unknown option: --" + argRest) +} + +func (o *Options) handleLongOption(args []string, i int, opt *option, argval *string) (skip int, err error) { + switch data := opt.data.(type) { + case *bool: + if argval == nil { + *data = true + } else { + switch *argval { + case "true", "on", "enabled", "1": + *data = true + case "false", "off", "disabled", "0": + *data = false + default: + return 0, optErr("invalid argument for option --" + opt.longName) + } + } + return 0, nil + case *FlagGroup: + if argval == nil { + return 1, data.parse("--"+opt.longName+"=", args[i+1]) + } else { + return 0, data.parse("--"+opt.longName+"=", *argval) + } + } + panic("getopt: unknown option type") +} + +func (o *Options) parseShortOptions(args []string, i int, optchars string) (skip int, err error) { +optchar: + for ai, optchar := range optchars { + for _, opt := range o.options { + if optchar == opt.shortName { + switch data := opt.data.(type) { + case *bool: + *data = true + continue optchar + case *FlagGroup: + argarg := optchars[ai+utf8.RuneLen(optchar):] + if argarg != "" { + return 0, data.parse(string([]rune{'-', optchar}), argarg) + } else if i+1 < len(args) { + return 1, data.parse(string([]rune{'-', optchar}), args[i+1]) + } else { + return 0, optErr("option requires an argument: -" + string([]rune{optchar})) + } + } + } + } + return 0, optErr("unknown option: -" + string([]rune{optchar})) + } + return 0, nil +} + +func (o *Options) Help(out io.Writer, generalUsage string) { + wr := tabwriter.NewWriter(out, 1, 0, 2, ' ', tabwriter.TabIndent) + + io.WriteString(wr, "usage: "+generalUsage+"\n") + io.WriteString(wr, "\n") + wr.Flush() + + for _, opt := range o.options { + if opt.argDescription == "" { + fmt.Fprintf(wr, " -%c, --%s\t %s\n", + opt.shortName, opt.longName, opt.description) + } else { + fmt.Fprintf(wr, " -%c, --%s=%s\t %s\n", + opt.shortName, opt.longName, opt.argDescription, opt.description) + } + } + wr.Flush() + + hasFlagGroups := false + for _, opt := range o.options { + switch flagGroup := opt.data.(type) { + case *FlagGroup: + hasFlagGroups = true + io.WriteString(wr, "\n") + fmt.Fprintf(wr, " Flags for -%c, --%s:\n", opt.shortName, opt.longName) + io.WriteString(wr, " all\t all of the following\n") + io.WriteString(wr, " none\t none of the following\n") + for _, flag := range flagGroup.flags { + state := "disabled" + if *flag.value { + state = "enabled" + } + fmt.Fprintf(wr, " %s\t %s (%v)\n", flag.name, flag.help, state) + } + wr.Flush() + } + } + if hasFlagGroups { + io.WriteString(wr, "\n") + io.WriteString(wr, " (Prefix a flag with \"no-\" to disable it.)\n") + wr.Flush() + } +} + +type option struct { + shortName rune + longName string + argDescription string + description string + data interface{} +} + +type FlagGroup struct { + flags []*groupFlag +} + +func (fg *FlagGroup) AddFlagVar(name string, flag *bool, defval bool, help string) { + opt := &groupFlag{name, flag, help} + fg.flags = append(fg.flags, opt) + *flag = defval +} + +func (fg *FlagGroup) parse(optionPrefix, arg string) (err error) { +argopt: + for _, argopt := range strings.Split(arg, ",") { + if argopt == "none" || argopt == "all" { + for _, opt := range fg.flags { + *opt.value = argopt == "all" + } + continue argopt + } + for _, opt := range fg.flags { + if argopt == opt.name { + *opt.value = true + continue argopt + } + if argopt == "no-"+opt.name { + *opt.value = false + continue argopt + } + } + return optErr("unknown option: " + optionPrefix + argopt) + } + return nil +} + +type groupFlag struct { + name string + value *bool + help string +} + +type optErr string + +func (err optErr) Error() string { + return string(err) +} Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.1 --- /dev/null Sun Jan 1 15:15:48 2017 +++ pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go Sun Jan 1 15:15:47 2017 @@ -0,0 +1,107 @@ +package getopt + +import ( + check "gopkg.in/check.v1" + "testing" +) + +type Suite struct { +} + +var _ = check.Suite(new(Suite)) + +func Test(t *testing.T) { check.TestingT(t) } + +func (s *Suite) Test_Options_Parse_short(c *check.C) { + opts := NewOptions() + var help bool + opts.AddFlagVar('h', "help", &help, false, "prints a help page") + + args, err := opts.Parse([]string{"progname", "-h"}) + + c.Assert(err, check.IsNil) + c.Check(args, check.IsNil) + c.Check(help, check.Equals, true) +} + +func (s *Suite) Test_Options_Parse_unknown_short(c *check.C) { + opts := NewOptions() + + _, err := opts.Parse([]string{"progname", "-z"}) + + c.Check(err.Error(), check.Equals, "progname: unknown option: -z") +} + +func (s *Suite) Test_Options_Parse_unknown_long(c *check.C) { + opts := NewOptions() + + _, err := opts.Parse([]string{"progname", "--unknown-long"}) + + c.Check(err.Error(), check.Equals, "progname: unknown option: --unknown-long") +} + +func (s *Suite) Test_Options_Parse_unknown_flag_in_group(c *check.C) { + opts := NewOptions() + opts.AddFlagGroup('W', "warnings", "", "") + + _, err := opts.Parse([]string{"progname", "-Wall", "-Werror"}) + + c.Check(err.Error(), check.Equals, "progname: unknown option: -Werror") + + _, err = opts.Parse([]string{"progname", "--warnings=all", "--warnings=no-error"}) + + c.Check(err.Error(), check.Equals, "progname: unknown option: --warnings=no-error") + + _, err = opts.Parse([]string{"progname", "-W"}) + + c.Check(err.Error(), check.Equals, "progname: option requires an argument: -W") +} + +func (s *Suite) Test_Options_Parse_abbreviated_long(c *check.C) { + opts := NewOptions() + var longFlag, longerFlag bool + opts.AddFlagVar('?', "long", &longFlag, false, "") + opts.AddFlagVar('?', "longer", &longerFlag, false, "") + + _, err := opts.Parse([]string{"progname", "--lo"}) + + c.Check(err.Error(), check.Equals, "progname: ambiguous option: --lo could mean --long or --longer") + + args, err := opts.Parse([]string{"progname", "--long"}) + + c.Assert(err, check.IsNil) + c.Check(args, check.IsNil) + c.Check(longFlag, check.Equals, true) + c.Check(longerFlag, check.Equals, false) + + longFlag = false + args, err = opts.Parse([]string{"progname", "--longe"}) + + c.Assert(err, check.IsNil) + c.Check(args, check.IsNil) + c.Check(longFlag, check.Equals, false) + c.Check(longerFlag, check.Equals, true) +} + +func (s *Suite) Test_Options_Parse_mixed_args_and_options(c *check.C) { + opts := NewOptions() + var aflag, bflag bool + opts.AddFlagVar('a', "aflag", &aflag, false, "") + opts.AddFlagVar('b', "bflag", &bflag, false, "") + + args, err := opts.Parse([]string{"progname", "-a", "arg1", "-b", "arg2"}) + + c.Assert(err, check.IsNil) + c.Check(args, check.DeepEquals, []string{"arg1", "arg2"}) + c.Check(aflag, check.Equals, true) + c.Check(bflag, check.Equals, true) + + aflag = false + bflag = false + args, err = opts.Parse([]string{"progname", "-a", "--", "arg1", "-b", "arg2"}) + + c.Assert(err, check.IsNil) + c.Check(args, check.DeepEquals, []string{"arg1", "-b", "arg2"}) + c.Check(aflag, check.Equals, true) + c.Check(bflag, check.Equals, false) +} Index: pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go:1.1 --- /dev/null Sun Jan 1 15:15:48 2017 +++ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go Sun Jan 1 15:15:47 2017 @@ -0,0 +1,105 @@ +package pkgver + +// See pkgtools/pkg_install/files/lib/dewey.c + +import ( + "strings" +) + +func imax(a, b int) int { + if a > b { + return a + } + return b +} +func icmp(a, b int) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +func Compare(left, right string) int { + lv := newVersion(left) + rv := newVersion(right) + + m := imax(len(lv.v), len(rv.v)) + for i := 0; i < m; i++ { + if c := icmp(lv.Place(i), rv.Place(i)); c != 0 { + return c + } + } + return icmp(lv.nb, rv.nb) +} + +type version struct { + v []int + nb int +} + +func newVersion(vstr string) *version { + v := new(version) + rest := strings.ToLower(vstr) + for rest != "" { + switch { + case isdigit(rest[0]): + n := 0 + i := 0 + for i < len(rest) && isdigit(rest[i]) { + n = 10*n + int(rest[i]-'0') + i++ + } + rest = rest[i:] + v.Add(n) + case rest[0] == '_' || rest[0] == '.': + v.Add(0) + rest = rest[1:] + case strings.HasPrefix(rest, "alpha"): + v.Add(-3) + rest = rest[5:] + case strings.HasPrefix(rest, "beta"): + v.Add(-2) + rest = rest[4:] + case strings.HasPrefix(rest, "pre"): + v.Add(-1) + rest = rest[3:] + case strings.HasPrefix(rest, "rc"): + v.Add(-1) + rest = rest[2:] + case strings.HasPrefix(rest, "pl"): + v.Add(0) + rest = rest[2:] + case strings.HasPrefix(rest, "nb"): + i := 2 + n := 0 + for i < len(rest) && isdigit(rest[i]) { + n = 10*n + int(rest[i]-'0') + i++ + } + v.nb = n + rest = rest[i:] + case rest[0]-'a' <= 'z'-'a': + v.Add(int(rest[0] - 'a' + 1)) + rest = rest[1:] + default: + rest = rest[1:] + } + } + return v +} + +func (v *version) Add(i int) { + v.v = append(v.v, i) +} +func isdigit(b byte) bool { + return b-'0' <= 9 +} +func (v *version) Place(i int) int { + if i < len(v.v) { + return v.v[i] + } + return 0 +} Index: pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go:1.1 --- /dev/null Sun Jan 1 15:15:48 2017 +++ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go Sun Jan 1 15:15:47 2017 @@ -0,0 +1,72 @@ +package pkgver + +import ( + check "gopkg.in/check.v1" + "testing" +) + +type Suite struct{} + +func Test(t *testing.T) { + check.Suite(&Suite{}) + check.TestingT(t) +} + +func (s *Suite) Test_newVersion(c *check.C) { + c.Check(newVersion("5.0"), check.DeepEquals, &version{[]int{5, 0, 0}, 0}) + c.Check(newVersion("5.0nb5"), check.DeepEquals, &version{[]int{5, 0, 0}, 5}) + c.Check(newVersion("0.0.1-SNAPSHOT"), check.DeepEquals, &version{[]int{0, 0, 0, 0, 1, 19, 14, 1, 16, 19, 8, 15, 20}, 0}) + c.Check(newVersion("1.0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0}) + c.Check(newVersion("2.5beta"), check.DeepEquals, &version{[]int{2, 0, 5, -2}, 0}) + c.Check(newVersion("20151110"), check.DeepEquals, &version{[]int{20151110}, 0}) + c.Check(newVersion("0"), check.DeepEquals, &version{[]int{0}, 0}) + c.Check(newVersion("nb1"), check.DeepEquals, &version{nil, 1}) + c.Check(newVersion("1.0.1a"), check.DeepEquals, &version{[]int{1, 0, 0, 0, 1, 1}, 0}) + c.Check(newVersion("1.0.1z"), check.DeepEquals, &version{[]int{1, 0, 0, 0, 1, 26}, 0}) + c.Check(newVersion("0pre20160620"), check.DeepEquals, &version{[]int{0, -1, 20160620}, 0}) +} + +func (s *Suite) Test_pkgverCmp(c *check.C) { + var versions = [][]string{ + {"0pre20160620"}, + {"0"}, + {"nb1"}, + {"0.0.1-SNAPSHOT"}, + {"1.0alpha"}, + {"1.0alpha3"}, + {"1", "1.0", "1.0.0"}, + {"1.0nb1"}, + {"1.0nb2"}, + {"1.0.1a"}, + {"1.0.1z"}, + {"2.0pre", "2.0rc"}, + {"2.0", "2.0pl"}, + {"2.0.1nb4"}, + {"2.0.1nb17"}, + {"2.5beta"}, + {"5.0"}, + {"5.0nb5"}, + {"5.5", "5.005"}, + {"20151110"}, + } + + for i, iversions := range versions { + for _, iversion := range iversions { + for j, jversions := range versions { + for _, jversion := range jversions { + actual := Compare(iversion, jversion) + if i < j && !(actual < 0) { + c.Check([]interface{}{i, iversion, j, jversion, "<0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual}) + } + if i == j && !(actual == 0) { + c.Check([]interface{}{i, iversion, j, jversion, "==0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual}) + } + if i > j && !(actual > 0) { + c.Check([]interface{}{i, iversion, j, jversion, ">0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual}) + } + } + } + + } + } +} --_----------=_1483283748252340--